diff --git a/.asf.yaml b/.asf.yaml index 2b64610591253..50611567f276b 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -18,6 +18,7 @@ # https://cwiki.apache.org/confluence/display/INFRA/.asf.yaml+features+for+git+repositories --- github: + del_branch_on_merge: true description: "Apache Superset is a Data Visualization and Data Exploration Platform" homepage: https://superset.apache.org/ labels: diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 871d48e406028..192959fc81339 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -12,7 +12,7 @@ # Notify Helm Chart maintainers about changes in it -/helm/superset/ @craig-rueda @dpgaspar @villebro @nytai @michael-s-molina @mistercrunch @rusackas +/helm/superset/ @craig-rueda @dpgaspar @villebro @nytai @michael-s-molina @mistercrunch @rusackas @Antonio-RiveroMartnez # Notify E2E test maintainers of changes @@ -24,7 +24,7 @@ # Notify PMC members of changes to required GitHub Actions -/.asf.yaml @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar +/.asf.yaml @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar @Antonio-RiveroMartnez # Maps are a finicky contribution process we care about diff --git a/.github/actions/setup-backend/action.yml b/.github/actions/setup-backend/action.yml index 2d2f993ffb20e..73345481d945d 100644 --- a/.github/actions/setup-backend/action.yml +++ b/.github/actions/setup-backend/action.yml @@ -26,11 +26,12 @@ runs: shell: bash run: | if [ "${{ inputs.python-version }}" = "current" ]; then - echo "PYTHON_VERSION=3.10" >> $GITHUB_ENV - elif [ "${{ inputs.python-version }}" = "next" ]; then echo "PYTHON_VERSION=3.11" >> $GITHUB_ENV + elif [ "${{ inputs.python-version }}" = "next" ]; then + # currently disabled in GHA matrixes because of library compatibility issues + echo "PYTHON_VERSION=3.12" >> $GITHUB_ENV elif [ "${{ inputs.python-version }}" = "previous" ]; then - echo "PYTHON_VERSION=3.9" >> $GITHUB_ENV + echo "PYTHON_VERSION=3.10" >> $GITHUB_ENV else echo "PYTHON_VERSION=${{ inputs.python-version }}" >> $GITHUB_ENV fi @@ -43,6 +44,7 @@ runs: run: | if [ "${{ inputs.install-superset }}" = "true" ]; then sudo apt-get update && sudo apt-get -y install libldap2-dev libsasl2-dev + pip install --upgrade pip setuptools wheel uv if [ "${{ inputs.requirements-type }}" = "dev" ]; then diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 72238d0e21853..df36d2f546dee 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -134,4 +134,4 @@ jobs: if: steps.check.outputs.docker shell: bash run: | - docker compose -f docker-compose-image-tag.yml up --exit-code-from superset-init + docker compose -f docker-compose-image-tag.yml up superset-init --exit-code-from superset-init diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index e102e630c813b..f3044353f7637 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-24.04 strategy: matrix: - python-version: ["current", "next", "previous"] + python-version: ["current", "previous"] steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v4 @@ -41,6 +41,8 @@ jobs: - name: pre-commit run: | set +e # Don't exit immediately on failure + # Skip eslint as it requires `npm ci` and is executed in another job + export SKIP=eslint pre-commit run --all-files if [ $? -ne 0 ] || ! git diff --quiet --exit-code; then echo "❌ Pre-commit check failed." diff --git a/.github/workflows/superset-docs-verify.yml b/.github/workflows/superset-docs-verify.yml index f3d04a33c06fc..b08bba091ae0a 100644 --- a/.github/workflows/superset-docs-verify.yml +++ b/.github/workflows/superset-docs-verify.yml @@ -28,7 +28,6 @@ jobs: linksToSkip: >- ^https://github.com/apache/(superset|incubator-superset)/(pull|issue)/\d+, http://localhost:8088/, - docker/.env-non-dev, http://127.0.0.1:3000/, http://localhost:9001/, https://charts.bitnami.com/bitnami, diff --git a/.github/workflows/superset-e2e.yml b/.github/workflows/superset-e2e.yml index b5646e8f41704..cbf9728f2381d 100644 --- a/.github/workflows/superset-e2e.yml +++ b/.github/workflows/superset-e2e.yml @@ -138,7 +138,7 @@ jobs: run: cypress-run-all ${{ env.USE_DASHBOARD }} - name: Upload Artifacts uses: actions/upload-artifact@v4 - if: github.event_name == 'workflow_dispatch' && (steps.check.outputs.python || steps.check.outputs.frontend) + if: failure() with: path: ${{ github.workspace }}/superset-frontend/cypress-base/cypress/screenshots name: cypress-artifact-${{ github.run_id }}-${{ github.job }} diff --git a/.github/workflows/superset-python-integrationtest.yml b/.github/workflows/superset-python-integrationtest.yml index a511882e6563d..3a7488966b1f0 100644 --- a/.github/workflows/superset-python-integrationtest.yml +++ b/.github/workflows/superset-python-integrationtest.yml @@ -77,7 +77,7 @@ jobs: runs-on: ubuntu-24.04 strategy: matrix: - python-version: ["current", "next", "previous"] + python-version: ["current", "previous"] env: PYTHONPATH: ${{ github.workspace }} SUPERSET_CONFIG: tests.integration_tests.superset_test_config diff --git a/.github/workflows/superset-python-unittest.yml b/.github/workflows/superset-python-unittest.yml index c7bb82a73363c..c4cef8de24c83 100644 --- a/.github/workflows/superset-python-unittest.yml +++ b/.github/workflows/superset-python-unittest.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-24.04 strategy: matrix: - python-version: ["current", "next"] + python-version: ["previous", "current"] env: PYTHONPATH: ${{ github.workspace }} steps: diff --git a/.gitignore b/.gitignore index 5fbfb941ac3c7..2652502a02639 100644 --- a/.gitignore +++ b/.gitignore @@ -50,7 +50,6 @@ env venv* env_py3 envpy3 -env36 local_config.py /superset_config.py /superset_text.yml @@ -66,7 +65,10 @@ superset-websocket/config.json *.js.map node_modules npm-debug.log* -superset/static/assets +superset/static/assets/* +!superset/static/assets/.gitkeep +superset/static/uploads/* +!superset/static/uploads/.gitkeep superset/static/version_info.json superset-frontend/**/esm/* superset-frontend/**/lib/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f4361616527e1..bb34ee4055280 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -52,6 +52,14 @@ repos: - id: trailing-whitespace exclude: ^.*\.(snap) args: ["--markdown-linebreak-ext=md"] + - repo: local + hooks: + - id: eslint + name: eslint + entry: bash -c 'cd superset-frontend && npm run eslint -- $(echo "$@" | sed "s|superset-frontend/||g")' + language: system + pass_filenames: true + files: \.(js|jsx|ts|tsx)$ - repo: https://github.com/pre-commit/mirrors-prettier rev: v4.0.0-alpha.8 # Use the sha or tag you want to point at hooks: diff --git a/Dockerfile b/Dockerfile index 7297ad139337b..76983ae773cf7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ ###################################################################### # Node stage to deal with static asset construction ###################################################################### -ARG PY_VER=3.10-slim-bookworm +ARG PY_VER=3.11-slim-bookworm # If BUILDPLATFORM is null, set it to 'amd64' (or leave as is otherwise). ARG BUILDPLATFORM=${BUILDPLATFORM:-amd64} diff --git a/RESOURCES/FEATURE_FLAGS.md b/RESOURCES/FEATURE_FLAGS.md index a09fb00b556bc..b5ab3f6d6fddc 100644 --- a/RESOURCES/FEATURE_FLAGS.md +++ b/RESOURCES/FEATURE_FLAGS.md @@ -62,6 +62,7 @@ These features flags are **safe for production**. They have been tested and will [//]: # "PLEASE KEEP THESE LISTS SORTED ALPHABETICALLY" ### Flags on the path to feature launch and flag deprecation/removal + - DASHBOARD_VIRTUALIZATION - DRILL_BY - DISABLE_LEGACY_DATASOURCE_EDITOR diff --git a/RESOURCES/INTHEWILD.md b/RESOURCES/INTHEWILD.md index febfed87b891a..7a19f37abdbd8 100644 --- a/RESOURCES/INTHEWILD.md +++ b/RESOURCES/INTHEWILD.md @@ -43,10 +43,12 @@ Join our growing community! - [Capital Service S.A.](https://capitalservice.pl) [@pkonarzewski] - [Clark.de](https://clark.de/) - [KarrotPay](https://www.daangnpay.com/) +- [Remita](https://remita.net) [@mujibishola] - [Taveo](https://www.taveo.com) [@codek] - [Unit](https://www.unit.co/about-us) [@amitmiran137] - [Wise](https://wise.com) [@koszti] - [Xendit](https://xendit.co/) [@LieAlbertTriAdrian] +- [Cover Genius](https://covergenius.com/) ### Gaming - [Popoko VM Games Studio](https://popoko.live) diff --git a/UPDATING.md b/UPDATING.md index 4267ae340a395..663df68cd9b69 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -24,6 +24,9 @@ assists people when migrating to a new version. ## Next +- [31894](https://github.com/apache/superset/pull/31894) Domain sharding is deprecated in favor of HTTP2. The `SUPERSET_WEBSERVER_DOMAINS` configuration will be removed in the next major version (6.0) +- [31774](https://github.com/apache/superset/pull/31774): Fixes the spelling of the `USE-ANALAGOUS-COLORS` feature flag. Please update any scripts/configuration item to use the new/corrected `USE-ANALOGOUS-COLORS` flag spelling. +- [31582](https://github.com/apache/superset/pull/31582) Removed the legacy Area, Bar, Event Flow, Heatmap, Histogram, Line, Sankey, and Sankey Loop charts. They were all automatically migrated to their ECharts counterparts with the exception of the Event Flow and Sankey Loop charts which were removed as they were not actively maintained and not widely used. If you were using the Event Flow or Sankey Loop charts, you will need to find an alternative solution. - [31198](https://github.com/apache/superset/pull/31198) Disallows by default the use of the following ClickHouse functions: "version", "currentDatabase", "hostName". - [29798](https://github.com/apache/superset/pull/29798) Since 3.1.0, the intial schedule for an alert or report was mistakenly offset by the specified timezone's relation to UTC. The initial schedule should now begin at the correct time. - [30021](https://github.com/apache/superset/pull/30021) The `dev` layer in our Dockerfile no long includes firefox binaries, only Chromium to reduce bloat/docker-build-time. @@ -31,6 +34,12 @@ assists people when migrating to a new version. - [31262](https://github.com/apache/superset/pull/31262) NOTE: deprecated `pylint` in favor of `ruff` as our only python linter. Only affect development workflows positively (not the release itself). It should cover most important rules, be much faster, but some things linting rules that were enforced before may not be enforce in the exact same way as before. - [31173](https://github.com/apache/superset/pull/31173) Modified `fetch_csrf_token` to align with HTTP standards, particularly regarding how cookies are handled. If you encounter any issues related to CSRF functionality, please report them as a new issue and reference this PR for context. - [31385](https://github.com/apache/superset/pull/31385) Significant docker refactor, reducing access levels for the `superset` user, streamlining layer building, ... +- [31503](https://github.com/apache/superset/pull/31503) Deprecating python 3.9.x support, 3.11 is now the recommended version and 3.10 is still supported over the Superset 5.0 lifecycle. +- [29121](https://github.com/apache/superset/pull/29121) Removed the `css`, `position_json`, and `json_metadata` from the payload of the dashboard list endpoint (`GET api/v1/dashboard`) for performance reasons. +- [29163](https://github.com/apache/superset/pull/29163) Removed the `SHARE_QUERIES_VIA_KV_STORE` and `KV_STORE` feature flags and changed the way Superset shares SQL Lab queries to use permalinks. The legacy `/kv` API was removed but we still support legacy links in 5.0. In 6.0, only permalinks will be supported. +- [25166](https://github.com/apache/superset/pull/25166) Changed the default configuration of `UPLOAD_FOLDER` from `/app/static/uploads/` to `/static/uploads/`. It also removed the unused `IMG_UPLOAD_FOLDER` and `IMG_UPLOAD_URL` configuration options. +- [30284](https://github.com/apache/superset/pull/30284) Deprecated GLOBAL_ASYNC_QUERIES_REDIS_CONFIG in favor of the new GLOBAL_ASYNC_QUERIES_CACHE_BACKEND configuration. To leverage Redis Sentinel, set CACHE_TYPE to RedisSentinelCache, or use RedisCache for standalone Redis + ### Potential Downtime diff --git a/docker-compose-image-tag.yml b/docker-compose-image-tag.yml index 868779bcc5d88..15dc6d3e7513a 100644 --- a/docker-compose-image-tag.yml +++ b/docker-compose-image-tag.yml @@ -22,9 +22,6 @@ # unique random secure passwords and SECRET_KEY. # ----------------------------------------------------------------------- x-superset-image: &superset-image apachesuperset.docker.scarf.sh/apache/superset:${TAG:-latest-dev} -x-superset-depends-on: &superset-depends-on - - db - - redis x-superset-volumes: &superset-volumes # /app/pythonpath_docker will be appended to the PYTHONPATH in the final container - ./docker:/app/docker @@ -64,8 +61,12 @@ services: restart: unless-stopped ports: - 8088:8088 - depends_on: *superset-depends-on + depends_on: + superset-init: + condition: service_completed_successfully volumes: *superset-volumes + environment: + SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}" superset-init: image: *superset-image @@ -76,11 +77,18 @@ services: required: true - path: docker/.env-local # optional override required: false - depends_on: *superset-depends-on + depends_on: + db: + condition: service_started + redis: + condition: service_started user: "root" volumes: *superset-volumes healthcheck: disable: true + environment: + SUPERSET_LOAD_EXAMPLES: "${SUPERSET_LOAD_EXAMPLES:-yes}" + SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}" superset-worker: image: *superset-image @@ -92,7 +100,9 @@ services: - path: docker/.env-local # optional override required: false restart: unless-stopped - depends_on: *superset-depends-on + depends_on: + superset-init: + condition: service_completed_successfully user: "root" volumes: *superset-volumes healthcheck: @@ -101,6 +111,8 @@ services: "CMD-SHELL", "celery -A superset.tasks.celery_app:app inspect ping -d celery@$$HOSTNAME", ] + environment: + SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}" superset-worker-beat: image: *superset-image @@ -112,11 +124,15 @@ services: - path: docker/.env-local # optional override required: false restart: unless-stopped - depends_on: *superset-depends-on + depends_on: + superset-init: + condition: service_completed_successfully user: "root" volumes: *superset-volumes healthcheck: disable: true + environment: + SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}" volumes: superset_home: diff --git a/docker-compose-non-dev.yml b/docker-compose-non-dev.yml index 7aa96a84276d7..5dc3bf2bc77f5 100644 --- a/docker-compose-non-dev.yml +++ b/docker-compose-non-dev.yml @@ -21,9 +21,6 @@ # create you own docker environment file (docker/.env) with your own # unique random secure passwords and SECRET_KEY. # ----------------------------------------------------------------------- -x-superset-depends-on: &superset-depends-on - - db - - redis x-superset-volumes: &superset-volumes # /app/pythonpath_docker will be appended to the PYTHONPATH in the final container - ./docker:/app/docker @@ -70,8 +67,12 @@ services: restart: unless-stopped ports: - 8088:8088 - depends_on: *superset-depends-on + depends_on: + superset-init: + condition: service_completed_successfully volumes: *superset-volumes + environment: + SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}" superset-init: container_name: superset_init @@ -83,11 +84,18 @@ services: required: true - path: docker/.env-local # optional override required: false - depends_on: *superset-depends-on + depends_on: + db: + condition: service_started + redis: + condition: service_started user: "root" volumes: *superset-volumes healthcheck: disable: true + environment: + SUPERSET_LOAD_EXAMPLES: "${SUPERSET_LOAD_EXAMPLES:-yes}" + SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}" superset-worker: build: @@ -100,7 +108,9 @@ services: - path: docker/.env-local # optional override required: false restart: unless-stopped - depends_on: *superset-depends-on + depends_on: + superset-init: + condition: service_completed_successfully user: "root" volumes: *superset-volumes healthcheck: @@ -109,6 +119,8 @@ services: "CMD-SHELL", "celery -A superset.tasks.celery_app:app inspect ping -d celery@$$HOSTNAME", ] + environment: + SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}" superset-worker-beat: build: @@ -121,11 +133,15 @@ services: - path: docker/.env-local # optional override required: false restart: unless-stopped - depends_on: *superset-depends-on + depends_on: + superset-init: + condition: service_completed_successfully user: "root" volumes: *superset-volumes healthcheck: disable: true + environment: + SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}" volumes: superset_home: diff --git a/docker-compose.yml b/docker-compose.yml index e248e973e6355..19c91f74a6d5c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,9 +22,6 @@ # unique random secure passwords and SECRET_KEY. # ----------------------------------------------------------------------- x-superset-user: &superset-user root -x-superset-depends-on: &superset-depends-on - - db - - redis x-superset-volumes: &superset-volumes # /app/pythonpath_docker will be appended to the PYTHONPATH in the final container - ./docker:/app/docker @@ -92,13 +89,18 @@ services: restart: unless-stopped ports: - 8088:8088 + # When in cypress-mode -> + - 8081:8081 extra_hosts: - "host.docker.internal:host-gateway" user: *superset-user - depends_on: *superset-depends-on + depends_on: + superset-init: + condition: service_completed_successfully volumes: *superset-volumes environment: CYPRESS_CONFIG: "${CYPRESS_CONFIG:-}" + SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}" superset-websocket: container_name: superset_websocket @@ -143,11 +145,17 @@ services: required: true - path: docker/.env-local # optional override required: false - depends_on: *superset-depends-on + depends_on: + db: + condition: service_started + redis: + condition: service_started user: *superset-user volumes: *superset-volumes environment: CYPRESS_CONFIG: "${CYPRESS_CONFIG:-}" + SUPERSET_LOAD_EXAMPLES: "${SUPERSET_LOAD_EXAMPLES:-yes}" + SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}" healthcheck: disable: true @@ -167,6 +175,10 @@ services: BUILD_SUPERSET_FRONTEND_IN_DOCKER: true NPM_RUN_PRUNE: false SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}" + # configuring the dev-server to use the host.docker.internal to connect to the backend + superset: "http://host.docker.internal:8088" + ports: + - "127.0.0.1:9000:9000" # exposing the dynamic webpack dev server container_name: superset_node command: ["/app/docker/docker-frontend.sh"] env_file: @@ -174,7 +186,6 @@ services: required: true - path: docker/.env-local # optional override required: false - depends_on: *superset-depends-on volumes: *superset-volumes superset-worker: @@ -189,8 +200,12 @@ services: required: false environment: CELERYD_CONCURRENCY: 2 + CYPRESS_CONFIG: "${CYPRESS_CONFIG:-}" + SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}" restart: unless-stopped - depends_on: *superset-depends-on + depends_on: + superset-init: + condition: service_completed_successfully user: *superset-user volumes: *superset-volumes extra_hosts: @@ -212,11 +227,15 @@ services: - path: docker/.env-local # optional override required: false restart: unless-stopped - depends_on: *superset-depends-on + depends_on: + - superset-worker user: *superset-user volumes: *superset-volumes healthcheck: disable: true + environment: + CYPRESS_CONFIG: "${CYPRESS_CONFIG:-}" + SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}" superset-tests-worker: build: @@ -237,8 +256,11 @@ services: REDIS_RESULTS_DB: 3 REDIS_HOST: localhost CELERYD_CONCURRENCY: 8 + SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}" network_mode: host - depends_on: *superset-depends-on + depends_on: + superset-init: + condition: service_completed_successfully user: *superset-user volumes: *superset-volumes healthcheck: diff --git a/docker/.env b/docker/.env index 7511766569340..c0e61e8f1d76d 100644 --- a/docker/.env +++ b/docker/.env @@ -15,6 +15,8 @@ # limitations under the License. # +# Allowing python to print() in docker +PYTHONUNBUFFERED=1 COMPOSE_PROJECT_NAME=superset DEV_MODE=true @@ -64,3 +66,4 @@ SUPERSET_SECRET_KEY=TEST_NON_DEV_SECRET ENABLE_PLAYWRIGHT=false PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true BUILD_SUPERSET_FRONTEND_IN_DOCKER=true +SUPERSET_LOG_LEVEL=info diff --git a/docker/README.md b/docker/README.md index be29bbec0e20c..a61e5b5bc9387 100644 --- a/docker/README.md +++ b/docker/README.md @@ -68,7 +68,7 @@ Don't forget to reload the page to take the new frontend into account though. ## 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`. +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. ## Resource Constraints diff --git a/docker/docker-bootstrap.sh b/docker/docker-bootstrap.sh index 1f7f17bb597df..5bcad1f4fea6d 100755 --- a/docker/docker-bootstrap.sh +++ b/docker/docker-bootstrap.sh @@ -26,11 +26,13 @@ if [ "$DEV_MODE" == "true" ]; then fi fi REQUIREMENTS_LOCAL="/app/docker/requirements-local.txt" +PORT=${PORT:-8088} # If Cypress run – overwrite the password for admin and export env variables if [ "$CYPRESS_CONFIG" == "true" ]; then - export SUPERSET_CONFIG=tests.integration_tests.superset_test_config export SUPERSET_TESTENV=true - export SUPERSET__SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://superset:superset@db:5432/superset + export POSTGRES_DB=superset_cypress + export SUPERSET__SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://superset:superset@db:5432/superset_cypress + PORT=8081 fi if [[ "$DATABASE_DIALECT" == postgres* ]] ; then echo "Installing postgres requirements" @@ -65,7 +67,7 @@ case "${1}" in ;; app) echo "Starting web app (using development server)..." - flask run -p 8088 --with-threads --reload --debugger --host=0.0.0.0 + flask run -p $PORT --with-threads --reload --debugger --host=0.0.0.0 ;; app-gunicorn) echo "Starting web app..." diff --git a/docker/docker-entrypoint-initdb.d/cypress-init.sh b/docker/docker-entrypoint-initdb.d/cypress-init.sh new file mode 100755 index 0000000000000..4e1bc76c735eb --- /dev/null +++ b/docker/docker-entrypoint-initdb.d/cypress-init.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# 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. + +# ------------------------------------------------------------------------ +# Creates the examples database and respective user. This database location +# and access credentials are defined on the environment variables +# ------------------------------------------------------------------------ +set -e + +psql -v ON_ERROR_STOP=1 --username "${POSTGRES_USER}" <<-EOSQL + CREATE DATABASE superset_cypress; +EOSQL diff --git a/docker/docker-frontend.sh b/docker/docker-frontend.sh index f851576730fce..d086908de664b 100755 --- a/docker/docker-frontend.sh +++ b/docker/docker-frontend.sh @@ -36,7 +36,9 @@ if [ "$BUILD_SUPERSET_FRONTEND_IN_DOCKER" = "true" ]; then npm install echo "Start webpack dev server" - npm run dev + # start the webpack dev server, serving dynamically at http://localhost:9000 + # it proxies to the backend served at http://localhost:8088 + npm run dev-server else echo "Skipping frontend build steps - YOU NEED TO RUN IT MANUALLY ON THE HOST!" diff --git a/docker/docker-init.sh b/docker/docker-init.sh index 4016fd898a038..f9bd09ed14d4d 100755 --- a/docker/docker-init.sh +++ b/docker/docker-init.sh @@ -30,24 +30,18 @@ fi echo_step() { cat < npm run cypress open -``` - -See [`superset-frontend/cypress_build.sh`](https://github.com/apache/superset/blob/master/superset-frontend/cypress_build.sh). - -As an alternative you can use docker compose environment for testing: - -Make sure you have added below line to your /etc/hosts file: -`127.0.0.1 db` - -If you already have launched Docker environment please use the following command to ensure a fresh database instance: -`docker compose down -v` - -Launch environment: - -`CYPRESS_CONFIG=true docker compose up --build` - -It will serve the backend and frontend on port 8088. - -Run Cypress tests: - -```bash -cd cypress-base -npm install -npm run cypress open -``` - ### Debugging Server App #### Local diff --git a/docs/docs/contributing/howtos.mdx b/docs/docs/contributing/howtos.mdx index 698d4ddf6f0ca..97626de0a8c00 100644 --- a/docs/docs/contributing/howtos.mdx +++ b/docs/docs/contributing/howtos.mdx @@ -225,34 +225,37 @@ npm run test -- path/to/file.js ### e2e Integration Testing -For e2e testing, we recommend that you use a `docker-compose` backed-setup - -Alternatively, you can go lower level and set things up in your -development environment by following these steps: - -First set up a python/flask backend: +For e2e testing, we recommend that you use a `docker compose` backend ```bash -export SUPERSET_CONFIG=tests.integration_tests.superset_test_config -export SUPERSET_TESTENV=true -export CYPRESS_BASE_URL="http://localhost:8081" -superset db upgrade -superset load_test_users -superset init -superset load-examples --load-test-data -superset run --port 8081 +CYPRESS_CONFIG=true docker compose up --build ``` +`docker compose` will get to work and expose a Cypress-ready Superset app. +This app uses a different database schema (`superset_cypress`) to keep it isolated from +your other dev environmen(s)t, a specific set of examples, and a set of configurations that +aligns with the expectations within the end-to-end tests. Also note that it's served on a +different port than the default port for the backend (`8088`). -In another terminal, prepare the frontend and run Cypress tests: +Now in another terminal, let's get ready to execute some Cypress commands. First, tell cypress +to connect to the Cypress-ready Superset backend. -```bash -cd superset-frontend -npm run build-instrumented +``` +CYPRESS_BASE_URL=http://localhost:8081 +``` -cd cypress-base +```bash +# superset-frontend/cypress-base is the base folder for everything Cypress-related +# It's essentially its own npm app, with its own dependencies, configurations and utilities +cd superset-frontend/cypress-base npm install -# run tests via headless Chrome browser (requires Chrome 64+) +# use interactive mode to run tests, while keeping memory usage contained +# this will fire up an interactive Cypress UI +# as you alter the code, the tests will re-run automatically, and you can visualize each +# and every step for debugging purposes +npx cypress open --config numTestsKeptInMemory=5 + +# to run the test suite on the command line using chrome (same as CI) npm run cypress-run-chrome # run tests from a specific file @@ -264,9 +267,6 @@ npm run cypress-run-chrome -- --spec cypress/e2e/dashboard/index.test.js --confi # to open the cypress ui npm run cypress-debug -# to point cypress to a url other than the default (http://localhost:8088) set the environment variable before running the script -# e.g., CYPRESS_BASE_URL="http://localhost:9000" -CYPRESS_BASE_URL= npm run cypress open ``` See [`superset-frontend/cypress_build.sh`](https://github.com/apache/superset/blob/master/superset-frontend/cypress_build.sh). diff --git a/pyproject.toml b/pyproject.toml index 50c83f4b75428..0dc3d3f64e9b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,13 +24,12 @@ name = "apache-superset" description = "A modern, enterprise-ready business intelligence web application" readme = "README.md" dynamic = ["version", "scripts", "entry-points"] -requires-python = ">=3.9" +requires-python = ">=3.10" license = { file="LICENSE.txt" } authors = [ { name = "Apache Software Foundation", email = "dev@superset.apache.org" }, ] classifiers = [ - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", ] @@ -45,7 +44,7 @@ dependencies = [ "cryptography>=42.0.4, <44.0.0", "deprecation>=2.1.0, <2.2.0", "flask>=2.2.5, <3.0.0", - "flask-appbuilder>=4.5.0, <5.0.0", + "flask-appbuilder>=4.5.3, <5.0.0", "flask-caching>=2.1.0, <3", "flask-compress>=1.13, <2.0", "flask-talisman>=1.0.0, <2.0", @@ -67,7 +66,7 @@ dependencies = [ "markdown>=3.0", "msgpack>=1.0.0, <1.1", "nh3>=0.2.11, <0.3", - "numpy==1.23.5", + "numpy>1.23.5, <2", "packaging", # -------------------------- # pandas and related (wanting pandas[performance] without numba as it's 100+MB and not needed) @@ -86,7 +85,7 @@ dependencies = [ "pyyaml>=6.0.0, <7.0.0", "PyJWT>=2.4.0, <3.0", "redis>=4.6.0, <5.0", - "selenium>=3.141.0, <4.10.0", + "selenium>=4.14.0, <5.0", "shillelagh[gsheetsapi]>=1.2.18, <2.0", "shortid", "sshtunnel>=0.4.0, <0.5", @@ -94,9 +93,7 @@ dependencies = [ "slack_sdk>=3.19.0, <4", "sqlalchemy>=1.4, <2", "sqlalchemy-utils>=0.38.3, <0.39", - # known breaking changes in sqlglot 25.25.0 - #https://github.com/tobymao/sqlglot/blob/main/CHANGELOG.md#v25250---2024-10-14 - "sqlglot>=25.24.0,<25.25.0", + "sqlglot>=26.1.3, <27", "sqlparse>=0.5.0", "tabulate>=0.8.9, <0.9", "typing-extensions>=4, <5", @@ -275,8 +272,8 @@ exclude = [ line-length = 88 indent-width = 4 -# Assume Python 3.9 -target-version = "py39" +# Assume Python 3.10 +target-version = "py310" [tool.ruff.lint] # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. diff --git a/requirements/base.in b/requirements/base.in index 17f5379cc837f..3cf35921545a1 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -23,8 +23,3 @@ numexpr>=2.9.0 # 5.0.0 has a sensitive deprecation used in other libs # -> https://github.com/aio-libs/async-timeout/blob/master/CHANGES.rst#500-2024-10-31 async_timeout>=4.0.0,<5.0.0 - -# playwright requires greenlet==3.0.3 -# submitted a PR to relax deps in 11/2024 -# https://github.com/microsoft/playwright-python/pull/2669 -greenlet==3.0.3 diff --git a/requirements/base.txt b/requirements/base.txt index c0cade94a6906..3ba7e3f98454f 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -9,14 +9,14 @@ apispec==6.3.0 apsw==3.46.0.0 # via shillelagh async-timeout==4.0.3 - # via - # -r requirements/base.in - # redis + # via -r requirements/base.in attrs==24.2.0 # via # cattrs # jsonschema + # outcome # requests-cache + # trio babel==2.16.0 # via flask-babel backoff==2.2.1 @@ -42,7 +42,9 @@ cattrs==24.1.2 celery==5.4.0 # via apache-superset (pyproject.toml) certifi==2024.8.30 - # via requests + # via + # requests + # selenium cffi==1.17.1 # via # cryptography @@ -92,8 +94,6 @@ email-validator==2.2.0 # via flask-appbuilder et-xmlfile==2.0.0 # via openpyxl -exceptiongroup==1.2.2 - # via cattrs flask==2.3.3 # via # apache-superset (pyproject.toml) @@ -108,7 +108,7 @@ flask==2.3.3 # flask-session # flask-sqlalchemy # flask-wtf -flask-appbuilder==4.5.2 +flask-appbuilder==4.5.3 # via apache-superset (pyproject.toml) flask-babel==2.0.0 # via flask-appbuilder @@ -146,11 +146,12 @@ google-auth==2.36.0 # via shillelagh greenlet==3.0.3 # via - # -r requirements/base.in # apache-superset (pyproject.toml) # shillelagh gunicorn==23.0.0 # via apache-superset (pyproject.toml) +h11==0.14.0 + # via wsproto hashids==1.3.1 # via apache-superset (pyproject.toml) holidays==0.25 @@ -161,6 +162,7 @@ idna==3.10 # via # email-validator # requests + # trio importlib-metadata==8.5.0 # via apache-superset (pyproject.toml) importlib-resources==6.4.5 @@ -215,7 +217,7 @@ nh3==0.2.19 # via apache-superset (pyproject.toml) numexpr==2.10.2 # via -r requirements/base.in -numpy==1.23.5 +numpy==1.26.4 # via # apache-superset (pyproject.toml) # bottleneck @@ -228,6 +230,8 @@ openpyxl==3.1.5 # via pandas ordered-set==4.1.0 # via flask-limiter +outcome==1.3.0.post0 + # via trio packaging==24.2 # via # apache-superset (pyproject.toml) @@ -283,6 +287,8 @@ pyparsing==3.2.0 # via apache-superset (pyproject.toml) pyrsistent==0.20.0 # via jsonschema +pysocks==1.7.1 + # via urllib3 python-dateutil==2.9.0.post0 # via # apache-superset (pyproject.toml) @@ -319,7 +325,7 @@ rich==13.9.4 # via flask-limiter rsa==4.9 # via google-auth -selenium==3.141.0 +selenium==4.27.1 # via apache-superset (pyproject.toml) shillelagh==1.2.18 # via apache-superset (pyproject.toml) @@ -335,6 +341,10 @@ six==1.16.0 # wtforms-json slack-sdk==3.33.4 # via apache-superset (pyproject.toml) +sniffio==1.3.1 + # via trio +sortedcontainers==2.4.0 + # via trio sqlalchemy==1.4.54 # via # apache-superset (pyproject.toml) @@ -348,7 +358,7 @@ sqlalchemy-utils==0.38.3 # via # apache-superset (pyproject.toml) # flask-appbuilder -sqlglot==25.24.5 +sqlglot==26.1.3 # via apache-superset (pyproject.toml) sqlparse==0.5.2 # via apache-superset (pyproject.toml) @@ -356,14 +366,19 @@ sshtunnel==0.4.0 # via apache-superset (pyproject.toml) tabulate==0.8.10 # via apache-superset (pyproject.toml) +trio==0.28.0 + # via + # selenium + # trio-websocket +trio-websocket==0.11.1 + # via selenium typing-extensions==4.12.2 # via # apache-superset (pyproject.toml) # alembic - # cattrs # flask-limiter # limits - # rich + # selenium # shillelagh tzdata==2024.2 # via @@ -385,6 +400,8 @@ vine==5.1.0 # kombu wcwidth==0.2.13 # via prompt-toolkit +websocket-client==1.8.0 + # via selenium werkzeug==3.1.3 # via # -r requirements/base.in @@ -394,6 +411,8 @@ werkzeug==3.1.3 # flask-login wrapt==1.17.0 # via deprecated +wsproto==1.2.0 + # via trio-websocket wtforms==3.2.1 # via # apache-superset (pyproject.toml) diff --git a/requirements/development.txt b/requirements/development.txt index 246f9e6cbded8..276ca4e20e7cf 100644 --- a/requirements/development.txt +++ b/requirements/development.txt @@ -18,16 +18,14 @@ apsw==3.46.0.0 # via # -c requirements/base.txt # shillelagh -async-timeout==4.0.3 - # via - # -c requirements/base.txt - # redis attrs==24.2.0 # via # -c requirements/base.txt # cattrs # jsonschema + # outcome # requests-cache + # trio babel==2.16.0 # via # -c requirements/base.txt @@ -77,6 +75,7 @@ certifi==2024.8.30 # via # -c requirements/base.txt # requests + # selenium cffi==1.17.1 # via # -c requirements/base.txt @@ -172,11 +171,6 @@ et-xmlfile==2.0.0 # via # -c requirements/base.txt # openpyxl -exceptiongroup==1.2.2 - # via - # -c requirements/base.txt - # cattrs - # pytest filelock==3.12.2 # via virtualenv flask==2.3.3 @@ -196,7 +190,7 @@ flask==2.3.3 # flask-sqlalchemy # flask-testing # flask-wtf -flask-appbuilder==4.5.2 +flask-appbuilder==4.5.3 # via # -c requirements/base.txt # apache-superset @@ -323,6 +317,10 @@ gunicorn==23.0.0 # via # -c requirements/base.txt # apache-superset +h11==0.14.0 + # via + # -c requirements/base.txt + # wsproto hashids==1.3.1 # via # -c requirements/base.txt @@ -343,6 +341,7 @@ idna==3.10 # -c requirements/base.txt # email-validator # requests + # trio importlib-metadata==8.5.0 # via # -c requirements/base.txt @@ -448,7 +447,7 @@ nh3==0.2.19 # apache-superset nodeenv==1.8.0 # via pre-commit -numpy==1.23.5 +numpy==1.26.4 # via # -c requirements/base.txt # apache-superset @@ -479,6 +478,10 @@ ordered-set==4.1.0 # via # -c requirements/base.txt # flask-limiter +outcome==1.3.0.post0 + # via + # -c requirements/base.txt + # trio packaging==24.2 # via # -c requirements/base.txt @@ -629,6 +632,10 @@ pyrsistent==0.20.0 # via # -c requirements/base.txt # jsonschema +pysocks==1.7.1 + # via + # -c requirements/base.txt + # urllib3 pytest==7.4.4 # via # apache-superset @@ -716,7 +723,7 @@ rsa==4.9 # google-auth ruff==0.8.0 # via apache-superset -selenium==3.141.0 +selenium==4.27.1 # via # -c requirements/base.txt # apache-superset @@ -751,6 +758,14 @@ slack-sdk==3.33.4 # via # -c requirements/base.txt # apache-superset +sniffio==1.3.1 + # via + # -c requirements/base.txt + # trio +sortedcontainers==2.4.0 + # via + # -c requirements/base.txt + # trio sqlalchemy==1.4.54 # via # -c requirements/base.txt @@ -769,7 +784,7 @@ sqlalchemy-utils==0.38.3 # -c requirements/base.txt # apache-superset # flask-appbuilder -sqlglot==25.24.5 +sqlglot==26.1.3 # via # -c requirements/base.txt # apache-superset @@ -789,25 +804,29 @@ tabulate==0.8.10 # via # -c requirements/base.txt # apache-superset -tomli==2.2.1 - # via - # coverage - # pytest tqdm==4.67.1 # via # cmdstanpy # prophet trino==0.330.0 # via apache-superset +trio==0.28.0 + # via + # -c requirements/base.txt + # selenium + # trio-websocket +trio-websocket==0.11.1 + # via + # -c requirements/base.txt + # selenium typing-extensions==4.12.2 # via # -c requirements/base.txt # alembic # apache-superset - # cattrs # flask-limiter # limits - # rich + # selenium # shillelagh tzdata==2024.2 # via @@ -840,6 +859,10 @@ wcwidth==0.2.13 # via # -c requirements/base.txt # prompt-toolkit +websocket-client==1.8.0 + # via + # -c requirements/base.txt + # selenium werkzeug==3.1.3 # via # -c requirements/base.txt @@ -851,6 +874,10 @@ wrapt==1.17.0 # via # -c requirements/base.txt # deprecated +wsproto==1.2.0 + # via + # -c requirements/base.txt + # trio-websocket wtforms==3.2.1 # via # -c requirements/base.txt diff --git a/scripts/cypress_run.py b/scripts/cypress_run.py index ca8b68cd35a69..7531aad8dafa3 100644 --- a/scripts/cypress_run.py +++ b/scripts/cypress_run.py @@ -20,7 +20,7 @@ import subprocess from datetime import datetime -XVFB_PRE_CMD = "xvfb-run --auto-servernum --server-args='-screen 0, 1024x768x24' " +XVFB_PRE_CMD = "xvfb-run --auto-servernum --server-args='-screen 0, 1280x1024x24' " REPO = os.getenv("GITHUB_REPOSITORY") or "apache/superset" GITHUB_EVENT_NAME = os.getenv("GITHUB_EVENT_NAME") or "push" CYPRESS_RECORD_KEY = os.getenv("CYPRESS_RECORD_KEY") or "" @@ -55,7 +55,9 @@ def run_cypress_for_test_file( group_id = f"matrix{group}-file{i}-{attempt}" cmd = ( f"{XVFB_PRE_CMD} " - f'{cypress_cmd} --spec "{test_file}" --browser {browser} ' + f'{cypress_cmd} --spec "{test_file}" ' + f"--config numTestsKeptInMemory=0 " + f"--browser {browser} " f"--record --group {group_id} --tag {REPO},{GITHUB_EVENT_NAME} " f"--ci-build-id {build_id} " f"-- {chrome_flags}" @@ -64,7 +66,9 @@ def run_cypress_for_test_file( os.environ.pop("CYPRESS_RECORD_KEY", None) cmd = ( f"{XVFB_PRE_CMD} " - f"{cypress_cmd} --browser {browser} " + f"{cypress_cmd} " + f"--browser {browser} " + f"--config numTestsKeptInMemory=0 " f'--spec "{test_file}" ' f"-- {chrome_flags}" ) diff --git a/superset-frontend/.eslintrc.js b/superset-frontend/.eslintrc.js index 9777651427545..d2ed09e69e75b 100644 --- a/superset-frontend/.eslintrc.js +++ b/superset-frontend/.eslintrc.js @@ -193,6 +193,12 @@ module.exports = { message: 'Default React import is not required due to automatic JSX runtime in React 16.4', }, + { + // this disallows wildcard imports from modules (but allows them for local files with `./` or `src/`) + selector: + 'ImportNamespaceSpecifier[parent.source.value!=/^(\\.|src)/]', + message: 'Wildcard imports are not allowed', + }, ], }, settings: { diff --git a/superset-frontend/cypress-base/cypress.config.ts b/superset-frontend/cypress-base/cypress.config.ts index 07aefdf677e07..2eaed82cb0a75 100644 --- a/superset-frontend/cypress-base/cypress.config.ts +++ b/superset-frontend/cypress-base/cypress.config.ts @@ -26,8 +26,9 @@ export default eyesPlugin( defineConfig({ chromeWebSecurity: false, defaultCommandTimeout: 8000, - numTestsKeptInMemory: 0, - experimentalFetchPolyfill: true, + numTestsKeptInMemory: 3, + // Disabled after realizing this MESSES UP rison encoding in intricate ways + experimentalFetchPolyfill: false, experimentalMemoryManagement: true, requestTimeout: 10000, video: false, @@ -57,11 +58,20 @@ export default eyesPlugin( }); launchOptions.args.push( - ...['--disable-dev-shm-usage', '--disable-gpu'], + '--disable-dev-shm-usage', + '--disable-gpu', + '--no-sandbox', + '--disable-software-rasterizer', + '--memory-pressure-off', + '--js-flags=--max-old-space-size=4096', + '--disable-background-timer-throttling', + '--disable-backgrounding-occluded-windows', + '--disable-renderer-backgrounding', ); } return launchOptions; }); + // eslint-disable-next-line global-require require('@cypress/code-coverage/task')(on, config); on('task', verifyDownloadTasks); @@ -70,6 +80,7 @@ export default eyesPlugin( }, baseUrl: 'http://localhost:8088', excludeSpecPattern: [], + experimentalRunAllSpecs: true, specPattern: [ 'cypress/e2e/**/*.{js,jsx,ts,tsx}', 'cypress/applitools/**/*.{js,jsx,ts,tsx}', diff --git a/superset-frontend/cypress-base/cypress/applitools/explore.test.ts b/superset-frontend/cypress-base/cypress/applitools/explore.test.ts index 142262262d954..233701cbfffaa 100644 --- a/superset-frontend/cypress-base/cypress/applitools/explore.test.ts +++ b/superset-frontend/cypress-base/cypress/applitools/explore.test.ts @@ -31,7 +31,10 @@ describe('explore view', () => { }); it('should load Explore', () => { - const LINE_CHART_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'line' }; + const LINE_CHART_DEFAULTS = { + ...FORM_DATA_DEFAULTS, + viz_type: 'echarts_timeseries_line', + }; const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] }; cy.visitChartByParams(formData); cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); diff --git a/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts b/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts index 4ea519acfff1c..a20e0499517a1 100644 --- a/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts @@ -26,6 +26,7 @@ import { visitSampleChartFromList, saveChartToDashboard, interceptFiltering, + interceptFavoriteStatus, } from '../explore/utils'; import { interceptGet as interceptDashboardGet } from '../dashboard/utils'; @@ -49,8 +50,10 @@ function confirmDelete() { function visitChartList() { interceptFiltering(); + interceptFavoriteStatus(); cy.visit(CHART_LIST); cy.wait('@filtering'); + cy.wait('@favoriteStatus'); } describe('Charts list', () => { @@ -78,20 +81,15 @@ describe('Charts list', () => { cy.wait('@get'); }); - it('should show the newly added dashboards in a tooltip', () => { + it.only('should show the newly added dashboards in a tooltip', () => { interceptDashboardGet(); visitSampleChartFromList('1 - Sample chart'); saveChartToDashboard('1 - Sample dashboard'); saveChartToDashboard('2 - Sample dashboard'); saveChartToDashboard('3 - Sample dashboard'); visitChartList(); + cy.getBySel('count-crosslinks').should('be.visible'); - cy.getBySel('crosslinks').first().trigger('mouseover'); - cy.get('.antd5-tooltip') - .contains('3 - Sample dashboard') - .invoke('removeAttr', 'target') - .click(); - cy.wait('@get'); }); }); @@ -116,7 +114,7 @@ describe('Charts list', () => { it('should sort correctly in list mode', () => { cy.getBySel('sort-header').eq(1).click(); - cy.getBySel('table-row').first().contains('% Rural'); + cy.getBySel('table-row').first().contains('Area Chart'); cy.getBySel('sort-header').eq(1).click(); cy.getBySel('table-row').first().contains("World's Population"); cy.getBySel('sort-header').eq(1).click(); diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/drillby.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/drillby.test.ts index 997372bae7f10..c4c5ed47665fb 100644 --- a/superset-frontend/cypress-base/cypress/e2e/dashboard/drillby.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/drillby.test.ts @@ -56,15 +56,21 @@ const drillBy = (targetDrillByColumn: string, isLegacy = false) => { cy.get('.ant-dropdown:not(.ant-dropdown-hidden)') .first() - .find("[role='menu'] [role='menuitem'] [title='Drill by']") + .should('be.visible') + .find("[role='menu'] [role='menuitem']") + .contains(/^Drill by$/) .trigger('mouseover', { force: true }); + cy.get( - '.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-hidden) [data-test="drill-by-submenu"]', + '.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]', ) + .should('be.visible') .find('[role="menuitem"]') - .contains(new RegExp(`^${targetDrillByColumn}$`)) - .first() - .click({ force: true }); + .then($el => { + cy.wrap($el) + .contains(new RegExp(`^${targetDrillByColumn}$`)) + .trigger('keydown', { keyCode: 13, which: 13, force: true }); + }); if (isLegacy) { return cy.wait('@legacyData'); @@ -524,8 +530,8 @@ describe('Drill by modal', () => { ]); }); - it('Bar Chart V2', () => { - testEchart('echarts_timeseries_bar', 'Bar Chart V2', [ + it('Bar Chart', () => { + testEchart('echarts_timeseries_bar', 'Bar Chart', [ [70, 94], [362, 68], ]); diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/drilltodetail.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/drilltodetail.test.ts index c876dbc24fbf9..f11aac445446b 100644 --- a/superset-frontend/cypress-base/cypress/e2e/dashboard/drilltodetail.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/drilltodetail.test.ts @@ -34,8 +34,8 @@ function openModalFromMenu(chartType: string) { cy.get( `[data-test-viz-type='${chartType}'] [aria-label='More Options']`, ).click(); - cy.get('.ant-dropdown') - .not('.ant-dropdown-hidden') + cy.get('.antd5-dropdown') + .not('.antd5-dropdown-hidden') .find("[role='menu'] [role='menuitem']") .eq(5) .should('contain', 'Drill to detail') @@ -43,37 +43,46 @@ function openModalFromMenu(chartType: string) { cy.wait('@samples'); } -function openModalFromChartContext(targetMenuItem: string) { +function drillToDetail(targetMenuItem: string) { interceptSamples(); - if (targetMenuItem.startsWith('Drill to detail by')) { - cy.get('.ant-dropdown') - .not('.ant-dropdown-hidden') - .should('be.visible') - .first() - .find("[role='menu'] [role='menuitem'] [title='Drill to detail by']") - .trigger('mouseover'); - cy.get('[data-test="drill-to-detail-by-submenu"]') - .should('be.visible') - .not('.ant-dropdown-menu-hidden [data-test="drill-to-detail-by-submenu"]') - .should('be.visible') - .find('[role="menuitem"]') - .contains(new RegExp(`^${targetMenuItem}$`)) - .first() - .click(); - } else { - cy.get('.ant-dropdown') - .not('.ant-dropdown-hidden') - .first() - .find("[role='menu'] [role='menuitem']") - .contains(new RegExp(`^${targetMenuItem}$`)) - .first() - .click(); - } + cy.get('.antd5-dropdown') + .not('.antd5-dropdown-hidden') + .first() + .find("[role='menu'] [role='menuitem']") + .contains(new RegExp(`^${targetMenuItem}$`)) + .first() + .trigger('keydown', { keyCode: 13, which: 13, force: true }); + cy.getBySel('metadata-bar').should('be.visible'); cy.wait('@samples'); } +const drillToDetailBy = (targetDrill: string) => { + interceptSamples(); + + cy.get('.ant-dropdown:not(.ant-dropdown-hidden)') + .first() + .should('be.visible') + .find("[role='menu'] [role='menuitem']") + .contains(/^Drill to detail by$/) + .trigger('mouseover', { force: true }); + + cy.get( + '.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-to-detail-by-submenu"]', + ) + .should('be.visible') + .find('[role="menuitem"]') + .then($el => { + cy.wrap($el) + .contains(new RegExp(`^${targetDrill}$`)) + .trigger('keydown', { keyCode: 13, which: 13, force: true }); + }); + + cy.getBySel('metadata-bar').should('be.visible'); + return cy.wait('@samples'); +}; + function closeModal() { cy.get('body').then($body => { if ($body.find('[data-test="close-drilltodetail-modal"]').length) { @@ -90,7 +99,7 @@ function testTimeChart(vizType: string) { cy.wrap($canvas).trigger('mousemove', 70, 93); cy.wrap($canvas).rightclick(70, 93); - openModalFromChartContext('Drill to detail by 1965'); + drillToDetailBy('Drill to detail by 1965'); cy.getBySel('filter-val').should('contain', '1965'); closeModal(); @@ -98,7 +107,7 @@ function testTimeChart(vizType: string) { cy.wrap($canvas).trigger('mousemove', 70, 93); cy.wrap($canvas).rightclick(70, 93); - openModalFromChartContext('Drill to detail by boy'); + drillToDetailBy('Drill to detail by boy'); cy.getBySel('filter-val').should('contain', 'boy'); closeModal(); @@ -106,24 +115,10 @@ function testTimeChart(vizType: string) { cy.wrap($canvas).trigger('mousemove', 70, 93); cy.wrap($canvas).rightclick(70, 93); - openModalFromChartContext('Drill to detail by all'); + drillToDetailBy('Drill to detail by all'); cy.getBySel('filter-val').first().should('contain', '1965'); cy.getBySel('filter-val').eq(1).should('contain', 'boy'); closeModal(); - - cy.wrap($canvas).scrollIntoView(); - cy.wrap($canvas).trigger('mousemove', 70, 145); - cy.wrap($canvas).rightclick(70, 145); - openModalFromChartContext('Drill to detail by girl'); - cy.getBySel('filter-val').should('contain', 'girl'); - closeModal(); - - cy.wrap($canvas).scrollIntoView(); - cy.wrap($canvas).trigger('mousemove', 70, 145); - cy.wrap($canvas).rightclick(70, 145); - openModalFromChartContext('Drill to detail by all'); - cy.getBySel('filter-val').first().should('contain', '1965'); - cy.getBySel('filter-val').eq(1).should('contain', 'girl'); }); } @@ -208,7 +203,7 @@ describe('Drill to detail modal', () => { "[data-test-viz-type='big_number_total'] .header-line", ).rightclick(); - openModalFromChartContext('Drill to detail'); + drillToDetail('Drill to detail'); cy.getBySel('filter-val').should('not.exist'); }); @@ -224,7 +219,7 @@ describe('Drill to detail modal', () => { ).scrollIntoView(); cy.get("[data-test-viz-type='big_number'] .header-line").rightclick(); - openModalFromChartContext('Drill to detail'); + drillToDetail('Drill to detail'); cy.getBySel('filter-val').should('not.exist'); @@ -236,7 +231,7 @@ describe('Drill to detail modal', () => { cy.wrap($canvas).trigger('mousemove', 1, 14); cy.wrap($canvas).rightclick(1, 14); - openModalFromChartContext('Drill to detail by 1965'); + drillToDetailBy('Drill to detail by 1965'); // checking the filter cy.getBySel('filter-val').should('contain', '1965'); @@ -255,7 +250,7 @@ describe('Drill to detail modal', () => { cy.get("[data-test-viz-type='table']").contains('boy').scrollIntoView(); cy.get("[data-test-viz-type='table']").contains('boy').rightclick(); - openModalFromChartContext('Drill to detail by boy'); + drillToDetailBy('Drill to detail by boy'); cy.getBySel('filter-val').should('contain', 'boy'); @@ -267,7 +262,7 @@ describe('Drill to detail modal', () => { cy.get("[data-test-viz-type='table']").scrollIntoView(); cy.get("[data-test-viz-type='table']").contains('girl').rightclick(); - openModalFromChartContext('Drill to detail by girl'); + drillToDetailBy('Drill to detail by girl'); cy.getBySel('filter-val').should('contain', 'girl'); }); @@ -283,7 +278,7 @@ describe('Drill to detail modal', () => { .first() .rightclick(); - openModalFromChartContext('Drill to detail by boy'); + drillToDetailBy('Drill to detail by boy'); cy.getBySel('filter-val').should('contain', 'boy'); closeModal(); @@ -294,7 +289,7 @@ describe('Drill to detail modal', () => { .first() .rightclick(); - openModalFromChartContext('Drill to detail by CA'); + drillToDetailBy('Drill to detail by CA'); cy.getBySel('filter-val').should('contain', 'CA'); closeModal(); @@ -305,7 +300,7 @@ describe('Drill to detail modal', () => { .eq(3) .rightclick(); - openModalFromChartContext('Drill to detail by girl'); + drillToDetailBy('Drill to detail by girl'); cy.getBySel('filter-val').should('contain', 'girl'); closeModal(); @@ -316,7 +311,7 @@ describe('Drill to detail modal', () => { .eq(3) .rightclick(); - openModalFromChartContext('Drill to detail by FL'); + drillToDetailBy('Drill to detail by FL'); cy.getBySel('filter-val').should('contain', 'FL'); closeModal(); @@ -327,7 +322,7 @@ describe('Drill to detail modal', () => { .eq(3) .rightclick(); - openModalFromChartContext('Drill to detail by all'); + drillToDetailBy('Drill to detail by all'); cy.getBySel('filter-val').first().should('contain', 'girl'); cy.getBySel('filter-val').eq(1).should('contain', 'FL'); @@ -349,21 +344,21 @@ describe('Drill to detail modal', () => { cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(70, 100); - openModalFromChartContext('Drill to detail by 1965'); + drillToDetailBy('Drill to detail by 1965'); cy.getBySel('filter-val').should('contain', '1965'); closeModal(); cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(70, 100); - openModalFromChartContext('Drill to detail by boy'); + drillToDetailBy('Drill to detail by boy'); cy.getBySel('filter-val').should('contain', 'boy'); closeModal(); cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(70, 100); - openModalFromChartContext('Drill to detail by all'); + drillToDetailBy('Drill to detail by all'); cy.getBySel('filter-val').first().should('contain', '1965'); cy.getBySel('filter-val').eq(1).should('contain', 'boy'); closeModal(); @@ -371,7 +366,7 @@ describe('Drill to detail modal', () => { cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(72, 200); - openModalFromChartContext('Drill to detail by girl'); + drillToDetailBy('Drill to detail by girl'); cy.getBySel('filter-val').should('contain', 'girl'); }, ); @@ -399,14 +394,14 @@ describe('Drill to detail modal', () => { cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(130, 150); - openModalFromChartContext('Drill to detail by girl'); + drillToDetailBy('Drill to detail by girl'); cy.getBySel('filter-val').should('contain', 'girl'); closeModal(); cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(230, 190); - openModalFromChartContext('Drill to detail by boy'); + drillToDetailBy('Drill to detail by boy'); cy.getBySel('filter-val').should('contain', 'boy'); }); }); @@ -419,31 +414,18 @@ describe('Drill to detail modal', () => { cy.get("[data-test-viz-type='world_map'] svg").then($canvas => { cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(70, 150); - openModalFromChartContext('Drill to detail by USA'); + drillToDetailBy('Drill to detail by USA'); cy.getBySel('filter-val').should('contain', 'USA'); closeModal(); }); cy.get("[data-test-viz-type='world_map'] svg").then($canvas => { cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(200, 140); - openModalFromChartContext('Drill to detail by SRB'); + drillToDetailBy('Drill to detail by SRB'); cy.getBySel('filter-val').should('contain', 'SRB'); }); }); }); - - describe('Bar Chart', () => { - it('opens the modal for unsupported chart without filters', () => { - interceptSamples(); - - cy.get("[data-test-viz-type='dist_bar'] svg").then($canvas => { - cy.wrap($canvas).scrollIntoView(); - cy.wrap($canvas).rightclick(70, 150); - openModalFromChartContext('Drill to detail'); - cy.getBySel('filter-val').should('not.exist'); - }); - }); - }); }); describe('Tier 2 charts', () => { @@ -469,7 +451,7 @@ describe('Drill to detail modal', () => { force: true, }); - openModalFromChartContext('Drill to detail by boy'); + drillToDetailBy('Drill to detail by boy'); // checking the filter cy.getBySel('filter-val').should('contain', 'boy'); @@ -505,7 +487,7 @@ describe('Drill to detail modal', () => { cy.wrap($canvas).trigger('mousemove', 135, 275); cy.wrap($canvas).rightclick(135, 275); - openModalFromChartContext('Drill to detail by boy'); + drillToDetailBy('Drill to detail by boy'); cy.getBySel('filter-val').should('contain', 'boy'); closeModal(); @@ -513,7 +495,7 @@ describe('Drill to detail modal', () => { cy.wrap($canvas).trigger('mousemove', 270, 280); cy.wrap($canvas).rightclick(270, 280); - openModalFromChartContext('Drill to detail by girl'); + drillToDetailBy('Drill to detail by girl'); cy.getBySel('filter-val').should('contain', 'girl'); }); }); @@ -545,14 +527,14 @@ describe('Drill to detail modal', () => { cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(170, 90); - openModalFromChartContext('Drill to detail by boy'); + drillToDetailBy('Drill to detail by boy'); cy.getBySel('filter-val').should('contain', 'boy'); closeModal(); cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(190, 250); - openModalFromChartContext('Drill to detail by girl'); + drillToDetailBy('Drill to detail by girl'); cy.getBySel('filter-val').should('contain', 'girl'); }); }); @@ -566,14 +548,14 @@ describe('Drill to detail modal', () => { cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(135, 95); - openModalFromChartContext('Drill to detail by boy'); + drillToDetailBy('Drill to detail by boy'); cy.getBySel('filter-val').should('contain', 'boy'); closeModal(); cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(95, 135); - openModalFromChartContext('Drill to detail by girl'); + drillToDetailBy('Drill to detail by girl'); cy.getBySel('filter-val').should('contain', 'girl'); }); }); @@ -593,14 +575,14 @@ describe('Drill to detail modal', () => { cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(180, 45); - openModalFromChartContext('Drill to detail by boy'); + drillToDetailBy('Drill to detail by boy'); cy.getBySel('filter-val').should('contain', 'boy'); closeModal(); cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(180, 85); - openModalFromChartContext('Drill to detail by girl'); + drillToDetailBy('Drill to detail by girl'); cy.getBySel('filter-val').should('contain', 'girl'); }); }); @@ -614,14 +596,14 @@ describe('Drill to detail modal', () => { cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(100, 30); - openModalFromChartContext('Drill to detail by boy'); + drillToDetailBy('Drill to detail by boy'); cy.getBySel('filter-val').should('contain', 'boy'); closeModal(); cy.wrap($canvas).scrollIntoView(); cy.wrap($canvas).rightclick(150, 250); - openModalFromChartContext('Drill to detail by girl'); + drillToDetailBy('Drill to detail by girl'); cy.getBySel('filter-val').should('contain', 'girl'); }); }); diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/editmode.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/editmode.test.ts index 0bc2ddc91babb..aaa047f601bc8 100644 --- a/superset-frontend/cypress-base/cypress/e2e/dashboard/editmode.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/editmode.test.ts @@ -16,13 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import { - SAMPLE_DASHBOARD_1, - SUPPORTED_CHARTS_DASHBOARD, - TABBED_DASHBOARD, -} from 'cypress/utils/urls'; +import { SAMPLE_DASHBOARD_1, TABBED_DASHBOARD } from 'cypress/utils/urls'; import { drag, resize, waitForChartLoad } from 'cypress/utils'; -import * as ace from 'brace'; +import { edit } from 'brace'; import { interceptExploreUpdate, interceptGet, @@ -30,7 +26,7 @@ import { openTab, } from './utils'; import { - interceptExploreJson, + interceptV1ChartData, interceptFiltering as interceptCharts, } from '../explore/utils'; @@ -55,14 +51,6 @@ function openProperties() { }); } -function openExploreProperties() { - cy.getBySel('actions-trigger').click({ force: true }); - cy.get('.ant-dropdown-menu') - .contains('Edit chart properties') - .click({ force: true }); - cy.get('.antd5-modal-body').should('be.visible'); -} - function assertMetadata(text: string) { const regex = new RegExp(text); cy.get('#json_metadata') @@ -72,7 +60,7 @@ function assertMetadata(text: string) { // cypress can read this locally, but not in ci // so we have to use the ace module directly to fetch the value - expect(ace.edit(metadata).getValue()).to.match(regex); + expect(edit(metadata).getValue()).to.match(regex); }); } @@ -225,19 +213,20 @@ function writeMetadata(metadata: string) { } function openExploreWithDashboardContext(chartName: string) { - interceptExploreJson(); + interceptV1ChartData(); interceptGet(); cy.get( `[data-test-chart-name='${chartName}'] [aria-label='More Options']`, ).click(); - cy.get('.ant-dropdown') - .not('.ant-dropdown-hidden') - .find("[role='menu'] [role='menuitem']") - .eq(2) - .should('contain', 'Edit chart') - .click(); - cy.wait('@getJson'); + cy.get(`[data-test-edit-chart-name='${chartName}']`) + .should('be.visible') + .trigger('keydown', { + keyCode: 13, + which: 13, + force: true, + }); + cy.wait('@v1Data'); cy.get('.chart-container').should('exist'); } @@ -255,13 +244,16 @@ function saveExploreColorScheme( cy.wait('@chartUpdate'); } +// FIXME: Skipping some tests as ECharts are rendered using Canvas and we cannot inspect the elements +// to verify the colors. We should revisit these tests once we have a solution to verify ECharts. + describe('Dashboard edit', () => { describe('Color consistency', () => { beforeEach(() => { resetDashboardColors(); }); - it('should not allow to change color scheme of a chart when dashboard has one', () => { + it.skip('should not allow to change color scheme of a chart when dashboard has one', () => { visitEdit(TABBED_DASHBOARD); openProperties(); selectColorScheme('blueToGreen'); @@ -272,11 +264,14 @@ describe('Dashboard edit', () => { openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); openExploreWithDashboardContext('Top 10 California Names Timeseries'); + // hover over canvas elements + cy.get('canvas').trigger('mouseover', { force: true }); + // label Anthony cy.get('[data-test="chart-container"] .line .nv-legend-symbol') .first() @@ -287,21 +282,21 @@ describe('Dashboard edit', () => { cy.get('[aria-label="Select color scheme"]').should('be.disabled'); }); - it('should not allow to change color scheme of a chart when dashboard has no scheme but chart has shared labels', () => { + it.skip('should not allow to change color scheme of a chart when dashboard has no scheme but chart has shared labels', () => { visit(TABBED_DASHBOARD); // open nested tab openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // open second top tab to catch shared labels openTab(0, 1); waitForChartLoad({ name: 'Trends', - viz: 'line', + viz: 'echarts_timeseries_line', }); openTab(0, 0); @@ -317,7 +312,7 @@ describe('Dashboard edit', () => { cy.get('[aria-label="Select color scheme"]').should('be.disabled'); }); - it('should allow to change color scheme of a chart when dashboard has no scheme but only custom label colors', () => { + it.skip('should allow to change color scheme of a chart when dashboard has no scheme but only custom label colors', () => { visitEdit(TABBED_DASHBOARD); openProperties(); openAdvancedProperties(); @@ -330,7 +325,7 @@ describe('Dashboard edit', () => { openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // label Anthony @@ -371,14 +366,14 @@ describe('Dashboard edit', () => { .should('have.css', 'fill', 'rgb(0, 116, 241)'); }); - it('should allow to change color scheme of a chart when dashboard has no scheme and show the change', () => { + it.skip('should allow to change color scheme of a chart when dashboard has no scheme and show the change', () => { visit(TABBED_DASHBOARD); // open nested tab openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // label Anthony @@ -414,7 +409,7 @@ describe('Dashboard edit', () => { saveExploreColorScheme(); }); - it('should allow to change color scheme of a chart when dashboard has no scheme but custom label colors and show the change', () => { + it.skip('should allow to change color scheme of a chart when dashboard has no scheme but custom label colors and show the change', () => { visitEdit(TABBED_DASHBOARD); openProperties(); openAdvancedProperties(); @@ -427,7 +422,7 @@ describe('Dashboard edit', () => { openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // label Anthony @@ -473,14 +468,14 @@ describe('Dashboard edit', () => { saveExploreColorScheme(); }); - it('should not change colors on refreshes with no color scheme set', () => { + it.skip('should not change colors on refreshes with no color scheme set', () => { visit(TABBED_DASHBOARD); // open nested tab openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // label Anthony @@ -492,7 +487,7 @@ describe('Dashboard edit', () => { // open 2nd main tab openTab(0, 1); - waitForChartLoad({ name: 'Trends', viz: 'line' }); + waitForChartLoad({ name: 'Trends', viz: 'echarts_timeseries_line' }); // label Andrew cy.get('[data-test-chart-name="Trends"] .line .nv-legend-symbol') @@ -505,7 +500,7 @@ describe('Dashboard edit', () => { openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // label Anthony @@ -517,7 +512,7 @@ describe('Dashboard edit', () => { // open 2nd main tab openTab(0, 1); - waitForChartLoad({ name: 'Trends', viz: 'line' }); + waitForChartLoad({ name: 'Trends', viz: 'echarts_timeseries_line' }); // label Andrew cy.get('[data-test-chart-name="Trends"] .line .nv-legend-symbol') @@ -525,7 +520,7 @@ describe('Dashboard edit', () => { .should('have.css', 'fill', 'rgb(69, 78, 124)'); }); - it('should not change colors on refreshes with color scheme set', () => { + it.skip('should not change colors on refreshes with color scheme set', () => { visitEdit(TABBED_DASHBOARD); openProperties(); selectColorScheme('blueToGreen'); @@ -536,7 +531,7 @@ describe('Dashboard edit', () => { openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // label Anthony @@ -548,7 +543,7 @@ describe('Dashboard edit', () => { // open 2nd main tab openTab(0, 1); - waitForChartLoad({ name: 'Trends', viz: 'line' }); + waitForChartLoad({ name: 'Trends', viz: 'echarts_timeseries_line' }); // label Andrew cy.get('[data-test-chart-name="Trends"] .line .nv-legend-symbol') @@ -561,7 +556,7 @@ describe('Dashboard edit', () => { openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // label Anthony @@ -573,7 +568,7 @@ describe('Dashboard edit', () => { // open 2nd main tab openTab(0, 1); - waitForChartLoad({ name: 'Trends', viz: 'line' }); + waitForChartLoad({ name: 'Trends', viz: 'echarts_timeseries_line' }); // label Andrew cy.get('[data-test-chart-name="Trends"] .line .nv-legend-symbol') @@ -581,14 +576,14 @@ describe('Dashboard edit', () => { .should('have.css', 'fill', 'rgb(0, 76, 218)'); }); - it('should respect chart color scheme when none is set for the dashboard', () => { + it.skip('should respect chart color scheme when none is set for the dashboard', () => { visit(TABBED_DASHBOARD); // open nested tab openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // label Anthony @@ -599,7 +594,7 @@ describe('Dashboard edit', () => { .should('have.css', 'fill', 'rgb(31, 168, 201)'); }); - it('should apply same color to same labels with color scheme set on refresh', () => { + it.skip('should apply same color to same labels with color scheme set on refresh', () => { visitEdit(TABBED_DASHBOARD); openProperties(); selectColorScheme('blueToGreen'); @@ -610,7 +605,7 @@ describe('Dashboard edit', () => { openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // label Anthony @@ -622,7 +617,7 @@ describe('Dashboard edit', () => { // open 2nd main tab openTab(0, 1); - waitForChartLoad({ name: 'Trends', viz: 'line' }); + waitForChartLoad({ name: 'Trends', viz: 'echarts_timeseries_line' }); // label Anthony cy.get('[data-test-chart-name="Trends"] .line .nv-legend-symbol') @@ -634,7 +629,7 @@ describe('Dashboard edit', () => { openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // label Anthony @@ -646,7 +641,7 @@ describe('Dashboard edit', () => { // open 2nd main tab openTab(0, 1); - waitForChartLoad({ name: 'Trends', viz: 'line' }); + waitForChartLoad({ name: 'Trends', viz: 'echarts_timeseries_line' }); // label Anthony cy.get('[data-test-chart-name="Trends"] .line .nv-legend-symbol') @@ -654,14 +649,14 @@ describe('Dashboard edit', () => { .should('have.css', 'fill', 'rgb(50, 0, 167)'); }); - it('should apply same color to same labels with no color scheme set on refresh', () => { + it.skip('should apply same color to same labels with no color scheme set on refresh', () => { visit(TABBED_DASHBOARD); // open nested tab openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // label Anthony @@ -673,7 +668,7 @@ describe('Dashboard edit', () => { // open 2nd main tab openTab(0, 1); - waitForChartLoad({ name: 'Trends', viz: 'line' }); + waitForChartLoad({ name: 'Trends', viz: 'echarts_timeseries_line' }); // label Anthony cy.get('[data-test-chart-name="Trends"] .line .nv-legend-symbol') @@ -686,7 +681,7 @@ describe('Dashboard edit', () => { openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // label Anthony @@ -698,7 +693,7 @@ describe('Dashboard edit', () => { // open 2nd main tab openTab(0, 1); - waitForChartLoad({ name: 'Trends', viz: 'line' }); + waitForChartLoad({ name: 'Trends', viz: 'echarts_timeseries_line' }); // label Anthony cy.get('[data-test-chart-name="Trends"] .line .nv-legend-symbol') @@ -706,7 +701,7 @@ describe('Dashboard edit', () => { .should('have.css', 'fill', 'rgb(31, 168, 201)'); }); - it('custom label colors should take the precedence in nested tabs', () => { + it.skip('custom label colors should take the precedence in nested tabs', () => { visitEdit(TABBED_DASHBOARD); openProperties(); openAdvancedProperties(); @@ -721,7 +716,7 @@ describe('Dashboard edit', () => { openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); cy.get( '[data-test-chart-name="Top 10 California Names Timeseries"] .line .nv-legend-symbol', @@ -731,19 +726,19 @@ describe('Dashboard edit', () => { // open another nested tab openTab(2, 1); - waitForChartLoad({ name: 'Growth Rate', viz: 'line' }); + waitForChartLoad({ name: 'Growth Rate', viz: 'echarts_timeseries_line' }); cy.get('[data-test-chart-name="Growth Rate"] .line .nv-legend-symbol') .first() .should('have.css', 'fill', 'rgb(255, 0, 0)'); }); - it('label colors should take the precedence for rendered charts in nested tabs', () => { + it.skip('label colors should take the precedence for rendered charts in nested tabs', () => { visitEdit(TABBED_DASHBOARD); // open the tab first time and let chart load openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // go to previous tab @@ -766,7 +761,7 @@ describe('Dashboard edit', () => { .should('have.css', 'fill', 'rgb(255, 0, 0)'); }); - it('should re-apply original color after removing custom label color with color scheme set', () => { + it.skip('should re-apply original color after removing custom label color with color scheme set', () => { visitEdit(TABBED_DASHBOARD); openProperties(); openAdvancedProperties(); @@ -809,13 +804,13 @@ describe('Dashboard edit', () => { .should('have.css', 'fill', 'rgb(41, 171, 226)'); }); - it('should re-apply original color after removing custom label color with no color scheme set', () => { + it.skip('should re-apply original color after removing custom label color with no color scheme set', () => { visitEdit(TABBED_DASHBOARD); // open nested tab openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); cy.get( '[data-test-chart-name="Top 10 California Names Timeseries"] .line .nv-legend-symbol', @@ -873,7 +868,7 @@ describe('Dashboard edit', () => { .should('have.css', 'fill', 'rgb(90, 193, 137)'); }); - it('should show the same colors in Explore', () => { + it.skip('should show the same colors in Explore', () => { visitEdit(TABBED_DASHBOARD); openProperties(); openAdvancedProperties(); @@ -888,7 +883,7 @@ describe('Dashboard edit', () => { openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // label Anthony @@ -906,7 +901,7 @@ describe('Dashboard edit', () => { .should('have.css', 'fill', 'rgb(255, 0, 0)'); }); - it('should change color scheme multiple times', () => { + it.skip('should change color scheme multiple times', () => { visitEdit(TABBED_DASHBOARD); openProperties(); selectColorScheme('blueToGreen'); @@ -917,7 +912,7 @@ describe('Dashboard edit', () => { openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // label Anthony @@ -929,7 +924,7 @@ describe('Dashboard edit', () => { // open 2nd main tab openTab(0, 1); - waitForChartLoad({ name: 'Trends', viz: 'line' }); + waitForChartLoad({ name: 'Trends', viz: 'echarts_timeseries_line' }); // label Anthony cy.get('[data-test-chart-name="Trends"] .line .nv-legend-symbol') @@ -959,7 +954,7 @@ describe('Dashboard edit', () => { .should('have.css', 'fill', 'rgb(0, 128, 246)'); }); - it('should apply the color scheme across main tabs', () => { + it.skip('should apply the color scheme across main tabs', () => { visitEdit(TABBED_DASHBOARD); openProperties(); selectColorScheme('blueToGreen'); @@ -968,14 +963,14 @@ describe('Dashboard edit', () => { // go to second tab openTab(0, 1); - waitForChartLoad({ name: 'Trends', viz: 'line' }); + waitForChartLoad({ name: 'Trends', viz: 'echarts_timeseries_line' }); cy.get('[data-test-chart-name="Trends"] .line .nv-legend-symbol') .first() .should('have.css', 'fill', 'rgb(50, 0, 167)'); }); - it('should apply the color scheme across main tabs for rendered charts', () => { + it.skip('should apply the color scheme across main tabs for rendered charts', () => { visitEdit(TABBED_DASHBOARD); waitForChartLoad({ name: 'Treemap', viz: 'treemap_v2' }); openProperties(); @@ -985,7 +980,7 @@ describe('Dashboard edit', () => { // go to second tab openTab(0, 1); - waitForChartLoad({ name: 'Trends', viz: 'line' }); + waitForChartLoad({ name: 'Trends', viz: 'echarts_timeseries_line' }); cy.get('[data-test-chart-name="Trends"] .line .nv-legend-symbol') .first() @@ -1003,7 +998,7 @@ describe('Dashboard edit', () => { .should('have.css', 'fill', 'rgb(0, 128, 246)'); }); - it('should apply the color scheme in nested tabs', () => { + it.skip('should apply the color scheme in nested tabs', () => { visitEdit(TABBED_DASHBOARD); openProperties(); selectColorScheme('blueToGreen'); @@ -1014,7 +1009,7 @@ describe('Dashboard edit', () => { openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); cy.get( '[data-test-chart-name="Top 10 California Names Timeseries"] .line .nv-legend-symbol', @@ -1024,19 +1019,19 @@ describe('Dashboard edit', () => { // open another nested tab openTab(2, 1); - waitForChartLoad({ name: 'Growth Rate', viz: 'line' }); + waitForChartLoad({ name: 'Growth Rate', viz: 'echarts_timeseries_line' }); cy.get('[data-test-chart-name="Growth Rate"] .line .nv-legend-symbol') .first() .should('have.css', 'fill', 'rgb(50, 0, 167)'); }); - it('should apply a valid color scheme for rendered charts in nested tabs', () => { + it.skip('should apply a valid color scheme for rendered charts in nested tabs', () => { visitEdit(TABBED_DASHBOARD); // open the tab first time and let chart load openTab(1, 1); waitForChartLoad({ name: 'Top 10 California Names Timeseries', - viz: 'line', + viz: 'echarts_timeseries_line', }); // go to previous tab diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/horizontalFilterBar.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/horizontalFilterBar.test.ts index 3cc1a2de6660e..f1bfa9617e1c3 100644 --- a/superset-frontend/cypress-base/cypress/e2e/dashboard/horizontalFilterBar.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/horizontalFilterBar.test.ts @@ -57,16 +57,16 @@ function setFilterBarOrientation(orientation: 'vertical' | 'horizontal') { .trigger('mouseover'); if (orientation === 'vertical') { - cy.get('.ant-dropdown-menu-item-selected') + cy.get('.antd5-menu-item-selected') .contains('Horizontal (Top)') .should('exist'); - cy.get('.ant-dropdown-menu-item').contains('Vertical (Left)').click(); + cy.get('.antd5-menu-item').contains('Vertical (Left)').click(); cy.getBySel('dashboard-filters-panel').should('exist'); } else { - cy.get('.ant-dropdown-menu-item-selected') + cy.get('.antd5-menu-item-selected') .contains('Vertical (Left)') .should('exist'); - cy.get('.ant-dropdown-menu-item').contains('Horizontal (Top)').click(); + cy.get('.antd5-menu-item').contains('Horizontal (Top)').click(); cy.getBySel('loading-indicator').should('exist'); cy.getBySel('filter-bar').should('exist'); cy.getBySel('dashboard-filters-panel').should('not.exist'); diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/tabs.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/tabs.test.ts index 39e937985db8c..ef4ea6419d4d3 100644 --- a/superset-frontend/cypress-base/cypress/e2e/dashboard/tabs.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/tabs.test.ts @@ -25,7 +25,7 @@ import { TABBED_DASHBOARD } from 'cypress/utils/urls'; import { expandFilterOnLeftPanel } from './utils'; const TREEMAP = { name: 'Treemap', viz: 'treemap_v2' }; -const LINE_CHART = { name: 'Growth Rate', viz: 'line' }; +const LINE_CHART = { name: 'Growth Rate', viz: 'echarts_timeseries_line' }; const BOX_PLOT = { name: 'Box plot', viz: 'box_plot' }; const BIG_NUMBER = { name: 'Number of Girls', viz: 'big_number_total' }; const TABLE = { name: 'Names Sorted by Num in California', viz: 'table' }; @@ -64,9 +64,8 @@ describe('Dashboard tabs', () => { cy.get('@top-level-tabs') .last() .should('not.have.class', 'ant-tabs-tab-active'); - - cy.getBySel('grid-container').find('.box_plot').should('not.exist'); - cy.getBySel('grid-container').find('.line').should('not.exist'); + cy.get('[data-test-chart-name="Box plot"]').should('not.exist'); + cy.get('[data-test-chart-name="Trends"]').should('not.exist'); cy.get('@top-level-tabs').last().click(); cy.get('@top-level-tabs') @@ -76,7 +75,8 @@ describe('Dashboard tabs', () => { .first() .should('not.have.class', 'ant-tabs-tab-active'); waitForChartLoad(BOX_PLOT); - cy.getBySel('grid-container').find('.box_plot').should('be.visible'); + + cy.get('[data-test-chart-name="Box plot"]').should('exist'); resetTabs(); @@ -88,7 +88,7 @@ describe('Dashboard tabs', () => { cy.get('@row-level-tabs').last().click(); waitForChartLoad(LINE_CHART); - cy.getBySel('grid-container').find('.line').should('be.visible'); + cy.get('[data-test-chart-name="Trends"]').should('exist'); cy.get('@row-level-tabs').first().click(); }); diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/utils.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/utils.ts index 655eaecc8a76a..fa85de54c04fd 100644 --- a/superset-frontend/cypress-base/cypress/e2e/dashboard/utils.ts +++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/utils.ts @@ -24,9 +24,9 @@ export const WORLD_HEALTH_CHARTS = [ { name: '% Rural', viz: 'world_map' }, { name: 'Most Populated Countries', viz: 'table' }, { name: "World's Population", viz: 'big_number' }, - { name: 'Growth Rate', viz: 'line' }, + { name: 'Growth Rate', viz: 'echarts_timeseries_line' }, { name: 'Rural Breakdown', viz: 'sunburst_v2' }, - { name: "World's Pop Growth", viz: 'area' }, + { name: "World's Pop Growth", viz: 'echarts_area' }, { name: 'Life Expectancy VS Rural %', viz: 'bubble' }, { name: 'Treemap', viz: 'treemap_v2' }, { name: 'Box plot', viz: 'box_plot' }, @@ -41,7 +41,7 @@ export const SUPPORTED_TIER1_CHARTS = [ { name: 'Line Chart', viz: 'echarts_timeseries_line' }, { name: 'Area Chart', viz: 'echarts_area' }, { name: 'Scatter Chart', viz: 'echarts_timeseries_scatter' }, - { name: 'Bar Chart V2', viz: 'echarts_timeseries_bar' }, + { name: 'Bar Chart', viz: 'echarts_timeseries_bar' }, ] as ChartSpec[]; export const SUPPORTED_TIER2_CHARTS = [ diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts index 1ee8b86bc0e88..77d0953edb667 100644 --- a/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts @@ -40,9 +40,25 @@ function openMenu() { cy.get('[aria-label="more-vert"]').first().click(); } -function confirmDelete() { - cy.getBySel('delete-modal-input').type('DELETE'); - cy.getBySel('modal-confirm-button').click(); +function confirmDelete(bulk = false) { + interceptDelete(); + interceptBulkDelete(); + + // Wait for modal dialog to be present and visible + cy.get('[role="dialog"][aria-modal="true"]').should('be.visible'); + cy.getBySel('delete-modal-input') + .should('be.visible') + .then($input => { + cy.wrap($input).clear(); + cy.wrap($input).type('DELETE'); + }); + cy.getBySel('modal-confirm-button').should('be.visible').click(); + + if (bulk) { + cy.wait('@bulkDelete'); + } else { + cy.wait('@delete'); + } } describe('Dashboards list', () => { @@ -157,7 +173,6 @@ describe('Dashboards list', () => { }); it('should bulk delete correctly', () => { - interceptBulkDelete(); toggleBulkSelect(); // bulk deletes in card-view @@ -167,8 +182,7 @@ describe('Dashboards list', () => { cy.getBySel('styled-card').eq(0).contains('1 - Sample dashboard').click(); cy.getBySel('styled-card').eq(1).contains('2 - Sample dashboard').click(); cy.getBySel('bulk-select-action').eq(0).contains('Delete').click(); - confirmDelete(); - cy.wait('@bulkDelete'); + confirmDelete(true); cy.getBySel('styled-card') .eq(0) .should('not.contain', '1 - Sample dashboard'); @@ -183,8 +197,7 @@ describe('Dashboards list', () => { cy.get('[data-test="table-row"] input[type="checkbox"]').eq(0).click(); cy.get('[data-test="table-row"] input[type="checkbox"]').eq(1).click(); cy.getBySel('bulk-select-action').eq(0).contains('Delete').click(); - confirmDelete(); - cy.wait('@bulkDelete'); + confirmDelete(true); cy.getBySel('table-row') .eq(0) .should('not.contain', '3 - Sample dashboard'); @@ -193,31 +206,36 @@ describe('Dashboards list', () => { .should('not.contain', '4 - Sample dashboard'); }); - it('should delete correctly', () => { - interceptDelete(); + it('should delete correctly in list mode', () => { + // deletes in list-view + setGridMode('list'); + cy.getBySel('table-row') + .eq(0) + .contains('4 - Sample dashboard') + .should('exist'); + cy.getBySel('dashboard-list-trash-icon').eq(0).click(); + confirmDelete(); + cy.getBySel('table-row') + .eq(0) + .should('not.contain', '4 - Sample dashboard'); + }); + + it('should delete correctly in card mode', () => { // deletes in card-view setGridMode('card'); orderAlphabetical(); - cy.getBySel('styled-card').eq(0).contains('1 - Sample dashboard'); + cy.getBySel('styled-card') + .eq(0) + .contains('1 - Sample dashboard') + .should('exist'); openMenu(); cy.getBySel('dashboard-card-option-delete-button').click(); confirmDelete(); - cy.wait('@delete'); cy.getBySel('styled-card') .eq(0) .should('not.contain', '1 - Sample dashboard'); - - // deletes in list-view - setGridMode('list'); - cy.getBySel('table-row').eq(0).contains('2 - Sample dashboard'); - cy.getBySel('dashboard-list-trash-icon').eq(0).click(); - confirmDelete(); - cy.wait('@delete'); - cy.getBySel('table-row') - .eq(0) - .should('not.contain', '2 - Sample dashboard'); }); it('should edit correctly', () => { diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/AdhocMetrics.test.ts b/superset-frontend/cypress-base/cypress/e2e/explore/AdhocMetrics.test.ts index f843f258f9d20..fc1d7a00b3dec 100644 --- a/superset-frontend/cypress-base/cypress/e2e/explore/AdhocMetrics.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/explore/AdhocMetrics.test.ts @@ -16,12 +16,13 @@ * specific language governing permissions and limitations * under the License. */ +import { interceptChart } from 'cypress/utils'; + describe('AdhocMetrics', () => { beforeEach(() => { - cy.intercept('POST', '/superset/explore_json/**').as('postJson'); - cy.intercept('GET', '/superset/explore_json/**').as('getJson'); + interceptChart({ legacy: false }).as('chartData'); cy.visitChartByName('Num Births Trend'); - cy.verifySliceSuccess({ waitAlias: '@postJson' }); + cy.verifySliceSuccess({ waitAlias: '@chartData' }); }); it('Clear metric and set simple adhoc metric', () => { @@ -52,9 +53,8 @@ describe('AdhocMetrics', () => { cy.get('button[data-test="run-query-button"]').click(); cy.verifySliceSuccess({ - waitAlias: '@postJson', + waitAlias: '@chartData', querySubstring: `${metric} AS "${metricName}"`, // SQL statement - chartSelector: 'svg', }); }); @@ -88,9 +88,8 @@ describe('AdhocMetrics', () => { const metric = 'SUM(num)/COUNT(DISTINCT name)'; cy.verifySliceSuccess({ - waitAlias: '@postJson', + waitAlias: '@chartData', querySubstring: `${metric} AS "${metric}"`, - chartSelector: 'svg', }); }); @@ -117,9 +116,8 @@ describe('AdhocMetrics', () => { const metric = 'SUM(num)'; cy.verifySliceSuccess({ - waitAlias: '@postJson', + waitAlias: '@chartData', querySubstring: `${metric} AS "${metric}"`, - chartSelector: 'svg', }); }); }); diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/advanced_analytics.test.ts b/superset-frontend/cypress-base/cypress/e2e/explore/advanced_analytics.test.ts index f07704c7bb53e..2d50460fd79fa 100644 --- a/superset-frontend/cypress-base/cypress/e2e/explore/advanced_analytics.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/explore/advanced_analytics.test.ts @@ -16,20 +16,21 @@ * specific language governing permissions and limitations * under the License. */ +import { interceptV1ChartData } from './utils'; + describe('Advanced analytics', () => { beforeEach(() => { - cy.intercept('POST', '/superset/explore_json/**').as('postJson'); - cy.intercept('GET', '/superset/explore_json/**').as('getJson'); + interceptV1ChartData(); cy.intercept('PUT', '/api/v1/explore/**').as('putExplore'); cy.intercept('GET', '/explore/**').as('getExplore'); }); it('Create custom time compare', () => { cy.visitChartByName('Num Births Trend'); - cy.verifySliceSuccess({ waitAlias: '@postJson' }); + cy.verifySliceSuccess({ waitAlias: '@v1Data' }); cy.get('.ant-collapse-header') - .contains('Advanced Analytics') + .contains('Advanced analytics') .click({ force: true }); cy.get('[data-test=time_compare]').find('.ant-select').click(); @@ -43,17 +44,16 @@ describe('Advanced analytics', () => { .type('1 year{enter}'); cy.get('button[data-test="run-query-button"]').click(); - cy.wait('@postJson'); + cy.wait('@v1Data'); cy.wait('@putExplore'); cy.reload(); cy.verifySliceSuccess({ - waitAlias: '@postJson', - chartSelector: 'svg', + waitAlias: '@v1Data', }); cy.wait('@getExplore'); cy.get('.ant-collapse-header') - .contains('Advanced Analytics') + .contains('Advanced analytics') .click({ force: true }); cy.get('[data-test=time_compare]') .find('.ant-select-selector') diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/annotations.test.ts b/superset-frontend/cypress-base/cypress/e2e/explore/annotations.test.ts index a10295af964fe..ec1596e932008 100644 --- a/superset-frontend/cypress-base/cypress/e2e/explore/annotations.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/explore/annotations.test.ts @@ -16,18 +16,22 @@ * specific language governing permissions and limitations * under the License. */ +import { interceptChart } from 'cypress/utils'; + describe('Annotations', () => { beforeEach(() => { - cy.intercept('POST', '/superset/explore_json/**').as('postJson'); - cy.intercept('GET', '/superset/explore_json/**').as('getJson'); + interceptChart({ legacy: false }).as('chartData'); }); it('Create formula annotation y-axis goal line', () => { cy.visitChartByName('Num Births Trend'); - cy.verifySliceSuccess({ waitAlias: '@postJson' }); + cy.verifySliceSuccess({ waitAlias: '@chartData' }); const layerLabel = 'Goal line'; + // get by text Annotations and Layers + cy.get('span').contains('Annotations and Layers').click(); + cy.get('[data-test=annotation_layers]').click(); cy.get('[data-test="popover-content"]').within(() => { @@ -39,10 +43,6 @@ describe('Annotations', () => { cy.get('button[data-test="run-query-button"]').click(); cy.get('[data-test=annotation_layers]').contains(layerLabel); - cy.verifySliceSuccess({ - waitAlias: '@postJson', - chartSelector: 'svg', - }); - cy.get('.nv-legend-text').should('have.length', 2); + cy.verifySliceSuccess({ waitAlias: '@chartData' }); }); }); diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/chart.test.js b/superset-frontend/cypress-base/cypress/e2e/explore/chart.test.js index 14c386e0ea62f..c7b81a3e12d34 100644 --- a/superset-frontend/cypress-base/cypress/e2e/explore/chart.test.js +++ b/superset-frontend/cypress-base/cypress/e2e/explore/chart.test.js @@ -21,6 +21,7 @@ import { interceptGet as interceptDashboardGet } from 'cypress/e2e/dashboard/uti import { FORM_DATA_DEFAULTS, NUM_METRIC } from './visualizations/shared.helper'; import { interceptFiltering, + interceptV1ChartData, saveChartToDashboard, visitSampleChartFromList, } from './utils'; @@ -124,14 +125,14 @@ describe('Cross-referenced dashboards', () => { describe('No Results', () => { beforeEach(() => { - cy.intercept('POST', '/superset/explore_json/**').as('getJson'); + interceptV1ChartData(); }); it('No results message shows up', () => { const formData = { ...FORM_DATA_DEFAULTS, metrics: [NUM_METRIC], - viz_type: 'line', + viz_type: 'echarts_timeseries_line', adhoc_filters: [ { expressionType: 'SIMPLE', @@ -145,7 +146,7 @@ describe('No Results', () => { }; cy.visitChartByParams(formData); - cy.wait('@getJson').its('response.statusCode').should('eq', 200); + cy.wait('@v1Data').its('response.statusCode').should('eq', 200); cy.get('div.chart-container').contains( 'No results were returned for this query', ); diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/control.test.ts b/superset-frontend/cypress-base/cypress/e2e/explore/control.test.ts index e4e7a7dc7e5e4..b68d828ba86bd 100644 --- a/superset-frontend/cypress-base/cypress/e2e/explore/control.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/explore/control.test.ts @@ -20,13 +20,12 @@ // Tests for setting controls in the UI // *********************************************** import { interceptChart } from 'cypress/utils'; -import { FORM_DATA_DEFAULTS, NUM_METRIC } from './visualizations/shared.helper'; describe('Datasource control', () => { const newMetricName = `abc${Date.now()}`; it('should allow edit dataset', () => { - interceptChart({ legacy: true }).as('chartData'); + interceptChart({ legacy: false }).as('chartData'); cy.visitChartByName('Num Births Trend'); cy.verifySliceSuccess({ waitAlias: '@chartData' }); @@ -85,7 +84,7 @@ describe('Datasource control', () => { describe('Color scheme control', () => { beforeEach(() => { - interceptChart({ legacy: true }).as('chartData'); + interceptChart({ legacy: false }).as('chartData'); cy.visitChartByName('Num Births Trend'); cy.verifySliceSuccess({ waitAlias: '@chartData' }); @@ -137,7 +136,7 @@ describe('VizType control', () => { describe('Test datatable', () => { beforeEach(() => { interceptChart({ legacy: false }).as('tableChartData'); - interceptChart({ legacy: true }).as('lineChartData'); + interceptChart({ legacy: false }).as('lineChartData'); cy.visitChartByName('Daily Totals'); }); it('Data Pane opens and loads results', () => { @@ -157,107 +156,9 @@ describe('Test datatable', () => { }); }); -describe('Time range filter', () => { - beforeEach(() => { - interceptChart({ legacy: true }).as('chartData'); - }); - - it('Advanced time_range params', () => { - const formData = { - ...FORM_DATA_DEFAULTS, - viz_type: 'line', - time_range: '100 years ago : now', - metrics: [NUM_METRIC], - }; - - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@chartData' }); - - cy.get('[data-test=time-range-trigger]').click(); - cy.get('.footer').find('button').its('length').should('eq', 2); - cy.get('.ant-popover-content').within(() => { - cy.get('input[value="100 years ago"]'); - cy.get('input[value="now"]'); - }); - cy.get('[data-test=cancel-button]').click(); - cy.wait(500); - cy.get('.ant-popover').should('not.exist'); - }); - - it('Common time_range params', () => { - const formData = { - ...FORM_DATA_DEFAULTS, - viz_type: 'line', - metrics: [NUM_METRIC], - time_range: 'Last year', - }; - - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@chartData' }); - - cy.get('[data-test=time-range-trigger]').click(); - cy.get('.ant-radio-group').children().its('length').should('eq', 5); - cy.get('.ant-radio-checked + span').contains('Last year'); - cy.get('[data-test=cancel-button]').click(); - }); - - it('Previous time_range params', () => { - const formData = { - ...FORM_DATA_DEFAULTS, - viz_type: 'line', - metrics: [NUM_METRIC], - time_range: 'previous calendar month', - }; - - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@chartData' }); - - cy.get('[data-test=time-range-trigger]').click(); - cy.get('.ant-radio-group').children().its('length').should('eq', 3); - cy.get('.ant-radio-checked + span').contains('previous calendar month'); - cy.get('[data-test=cancel-button]').click(); - }); - - it('Custom time_range params', () => { - const formData = { - ...FORM_DATA_DEFAULTS, - viz_type: 'line', - metrics: [NUM_METRIC], - time_range: 'DATEADD(DATETIME("today"), -7, day) : today', - }; - - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@chartData' }); - - cy.get('[data-test=time-range-trigger]').click(); - cy.get('[data-test=custom-frame]').then(() => { - cy.get('.antd5-input-number-input-wrap > input') - .invoke('attr', 'value') - .should('eq', '7'); - }); - cy.get('[data-test=cancel-button]').click(); - }); - - it('No filter time_range params', () => { - const formData = { - ...FORM_DATA_DEFAULTS, - viz_type: 'line', - metrics: [NUM_METRIC], - time_range: 'No filter', - }; - - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@chartData' }); - - cy.get('[data-test=time-range-trigger]').click(); - cy.get('[data-test=no-filter]').should('exist'); - cy.get('[data-test=cancel-button]').click(); - }); -}); - describe('Groupby control', () => { it('Set groupby', () => { - interceptChart({ legacy: true }).as('chartData'); + interceptChart({ legacy: false }).as('chartData'); cy.visitChartByName('Num Births Trend'); cy.verifySliceSuccess({ waitAlias: '@chartData' }); @@ -271,6 +172,6 @@ describe('Groupby control', () => { cy.get('[data-test="ColumnEdit#save"]').contains('Save').click(); cy.get('button[data-test="run-query-button"]').click(); - cy.verifySliceSuccess({ waitAlias: '@chartData', chartSelector: 'svg' }); + cy.verifySliceSuccess({ waitAlias: '@chartData' }); }); }); diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/link.test.ts b/superset-frontend/cypress-base/cypress/e2e/explore/link.test.ts index 5c7be1c968dc8..e768e5a8c9cc3 100644 --- a/superset-frontend/cypress-base/cypress/e2e/explore/link.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/explore/link.test.ts @@ -30,7 +30,7 @@ const apiURL = (endpoint: string, queryObject: Record) => describe('Test explore links', () => { beforeEach(() => { - interceptChart({ legacy: true }).as('chartData'); + interceptChart({ legacy: false }).as('chartData'); }); it('Open and close view query modal', () => { @@ -52,11 +52,10 @@ describe('Test explore links', () => { cy.verifySliceSuccess({ waitAlias: '@chartData' }); cy.get('[aria-label="Menu actions trigger"]').click(); - cy.get('div[title="Share"]').trigger('mouseover'); - // need to use [id= syntax, otherwise error gets triggered because of special character in id - cy.get('[id="share_submenu$Menu"]').within(() => { - cy.contains('Embed code').parent().click(); + cy.get('div[role="menuitem"]').within(() => { + cy.contains('Share').parent().click(); }); + cy.getBySel('embed-code-button').click(); cy.get('#embed-code-popover').within(() => { cy.get('textarea[name=embedCode]').contains('iframe'); }); diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/utils.ts b/superset-frontend/cypress-base/cypress/e2e/explore/utils.ts index 95af89db5fb51..298453a58e7d8 100644 --- a/superset-frontend/cypress-base/cypress/e2e/explore/utils.ts +++ b/superset-frontend/cypress-base/cypress/e2e/explore/utils.ts @@ -31,6 +31,10 @@ export function interceptDelete() { cy.intercept('DELETE', `/api/v1/chart/*`).as('delete'); } +export function interceptFavoriteStatus() { + cy.intercept('GET', '/api/v1/chart/favorite_status/*').as('favoriteStatus'); +} + export function interceptUpdate() { cy.intercept('PUT', `/api/v1/chart/*`).as('update'); } @@ -68,7 +72,10 @@ export function saveChartToDashboard(dashboardName: string) { interceptUpdate(); interceptExploreGet(); - cy.getBySel('query-save-button').click(); + cy.getBySel('query-save-button') + .should('be.enabled') + .should('not.be.disabled') + .click(); cy.getBySelLike('chart-modal').should('be.visible'); cy.get( '[data-test="save-chart-modal-select-dashboard-form"] [aria-label="Select a dashboard"]', @@ -77,7 +84,7 @@ export function saveChartToDashboard(dashboardName: string) { .click(); cy.get( '.ant-select-selection-search-input[aria-label="Select a dashboard"]', - ).type(dashboardName.slice(0, 3), { force: true }); + ).type(dashboardName, { force: true }); cy.get(`.ant-select-item-option[title="${dashboardName}"]`).click(); cy.getBySel('btn-modal-save').click(); diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/area.test.js b/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/area.test.js deleted file mode 100644 index 78981ed02813a..0000000000000 --- a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/area.test.js +++ /dev/null @@ -1,120 +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. - */ -describe('Visualization > Area', () => { - beforeEach(() => { - cy.intercept('POST', '/superset/explore_json/**').as('getJson'); - }); - - const AREA_FORM_DATA = { - datasource: '2__table', - viz_type: 'area', - slice_id: 48, - granularity_sqla: 'year', - time_grain_sqla: 'P1D', - time_range: '1960-01-01 : now', - metrics: ['sum__SP_POP_TOTL'], - adhoc_filters: [], - groupby: [], - limit: '25', - order_desc: true, - contribution: false, - row_limit: 50000, - show_brush: 'auto', - show_legend: true, - line_interpolation: 'linear', - stacked_style: 'stack', - color_scheme: 'bnbColors', - rich_tooltip: true, - show_controls: false, - x_axis_label: '', - bottom_margin: 'auto', - x_ticks_layout: 'auto', - x_axis_format: 'smart_date', - x_axis_showminmax: false, - y_axis_format: '.3s', - y_log_scale: false, - rolling_type: 'None', - comparison_type: 'values', - annotation_layers: [], - }; - - function verify(formData) { - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - } - - it('should work without groupby', () => { - verify(AREA_FORM_DATA); - cy.get('.nv-area').should('have.length', 1); - }); - - it('should work with group by', () => { - verify({ - ...AREA_FORM_DATA, - groupby: ['region'], - }); - - cy.get('.nv-area').should('have.length', 7); - }); - - it('should work with groupby and filter', () => { - cy.visitChartByParams({ - ...AREA_FORM_DATA, - groupby: ['region'], - adhoc_filters: [ - { - expressionType: 'SIMPLE', - subject: 'region', - operator: 'IN', - comparator: ['South Asia', 'North America'], - clause: 'WHERE', - sqlExpression: null, - filterOptionName: 'filter_txje2ikiv6_wxmn0qwd1xo', - }, - ], - }); - - cy.wait('@getJson').then(async ({ response }) => { - const responseBody = response?.body; - // Make sure data is sorted correctly - const firstRow = responseBody.data[0].values; - const secondRow = responseBody.data[1].values; - expect(firstRow[firstRow.length - 1].y).to.be.greaterThan( - secondRow[secondRow.length - 1].y, - ); - cy.verifySliceContainer('svg'); - }); - cy.get('.nv-area').should('have.length', 2); - }); - - it('should allow type to search color schemes and apply the scheme', () => { - verify(AREA_FORM_DATA); - - cy.get('#controlSections-tab-display').click(); - cy.get('.Control[data-test="color_scheme"]').scrollIntoView(); - cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus(); - cy.focused().type('supersetColors{enter}'); - cy.get( - '.Control[data-test="color_scheme"] .ant-select-selection-item [data-test="supersetColors"]', - ).should('exist'); - cy.get('.area .nv-legend .nv-legend-symbol') - .first() - .should('have.css', 'fill', 'rgb(31, 168, 201)'); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/dist_bar.test.js b/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/dist_bar.test.js deleted file mode 100644 index e208b7892257b..0000000000000 --- a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/dist_bar.test.js +++ /dev/null @@ -1,90 +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 { FORM_DATA_DEFAULTS, NUM_METRIC } from './shared.helper'; - -describe('Visualization > Distribution bar chart', () => { - beforeEach(() => { - cy.intercept('POST', '/superset/explore_json/**').as('getJson'); - }); - - const VIZ_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'dist_bar' }; - const DISTBAR_FORM_DATA = { - ...VIZ_DEFAULTS, - metrics: NUM_METRIC, - groupby: ['state'], - }; - - it('should work with adhoc metric', () => { - cy.visitChartByParams(DISTBAR_FORM_DATA); - cy.verifySliceSuccess({ - waitAlias: '@getJson', - querySubstring: NUM_METRIC.label, - chartSelector: 'svg', - }); - }); - - it('should work with series', () => { - const formData = { - ...VIZ_DEFAULTS, - metrics: NUM_METRIC, - groupby: ['state'], - columns: ['gender'], - }; - - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - }); - - it('should work with row limit', () => { - const formData = { - ...VIZ_DEFAULTS, - metrics: NUM_METRIC, - groupby: ['state'], - row_limit: 10, - }; - - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - }); - - it('should work with contribution', () => { - const formData = { - ...VIZ_DEFAULTS, - metrics: NUM_METRIC, - groupby: ['state'], - columns: ['gender'], - contribution: true, - }; - - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - }); - - it('should allow type to search color schemes and apply the scheme', () => { - cy.visitChartByParams(DISTBAR_FORM_DATA); - - cy.get('#controlSections-tab-display').click(); - cy.get('.Control[data-test="color_scheme"]').scrollIntoView(); - cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus(); - cy.focused().type('bnbColors{enter}'); - cy.get( - '.Control[data-test="color_scheme"] .ant-select-selection-item [data-test="bnbColors"]', - ).should('exist'); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/download_chart.test.js b/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/download_chart.test.js index 668e9c789f617..ce7e522774019 100644 --- a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/download_chart.test.js +++ b/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/download_chart.test.js @@ -18,8 +18,11 @@ */ import { FORM_DATA_DEFAULTS, NUM_METRIC } from './shared.helper'; -describe('Download Chart > Distribution bar chart', () => { - const VIZ_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'dist_bar' }; +describe('Download Chart > Bar chart', () => { + const VIZ_DEFAULTS = { + ...FORM_DATA_DEFAULTS, + viz_type: 'echarts_timeseries_bar', + }; beforeEach(() => { cy.intercept('POST', '/superset/explore_json/**').as('getJson'); diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/histogram.test.ts b/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/histogram.test.ts deleted file mode 100644 index 7904bd970f4f1..0000000000000 --- a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/histogram.test.ts +++ /dev/null @@ -1,102 +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 { QueryFormData } from '@superset-ui/core'; - -describe('Visualization > Histogram', () => { - beforeEach(() => { - cy.intercept('POST', '/superset/explore_json/**').as('getJson'); - }); - - const HISTOGRAM_FORM_DATA: QueryFormData = { - datasource: '3__table', - viz_type: 'histogram', - slice_id: 60, - granularity_sqla: 'ds', - time_grain_sqla: 'P1D', - time_range: '100 years ago : now', - all_columns_x: ['num'], - adhoc_filters: [], - row_limit: 50000, - groupby: [], - color_scheme: 'bnbColors', - link_length: 5, // number of bins - x_axis_label: 'Frequency', - y_axis_label: 'Num', - global_opacity: 1, - normalized: false, - }; - - function verify(formData: QueryFormData) { - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - } - - it('should work without groupby', () => { - verify(HISTOGRAM_FORM_DATA); - cy.get('.chart-container svg .vx-bar').should( - 'have.length', - HISTOGRAM_FORM_DATA.link_length, - ); - }); - - it('should work with group by', () => { - verify({ - ...HISTOGRAM_FORM_DATA, - groupby: ['gender'], - }); - cy.get('.chart-container svg .vx-bar').should( - 'have.length', - HISTOGRAM_FORM_DATA.link_length * 2, - ); - }); - - it('should work with filter and update num bins', () => { - const numBins = 2; - verify({ - ...HISTOGRAM_FORM_DATA, - link_length: numBins, - adhoc_filters: [ - { - expressionType: 'SIMPLE', - clause: 'WHERE', - subject: 'state', - operator: '==', - comparator: 'CA', - }, - ], - }); - cy.get('.chart-container svg .vx-bar').should('have.length', numBins); - }); - - it('should allow type to search color schemes and apply the scheme', () => { - verify(HISTOGRAM_FORM_DATA); - - cy.get('#controlSections-tab-display').click(); - cy.get('.Control[data-test="color_scheme"]').scrollIntoView(); - cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus(); - cy.focused().type('supersetColors{enter}'); - cy.get( - '.Control[data-test="color_scheme"] .ant-select-selection-item [data-test="supersetColors"]', - ).should('exist'); - cy.get('.histogram .vx-legend .vx-legend-shape div') - .first() - .should('have.css', 'background') - .and('contains', 'rgb(31, 168, 201)'); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/line.test.ts b/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/line.test.ts deleted file mode 100644 index ef7b9b53dad47..0000000000000 --- a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/line.test.ts +++ /dev/null @@ -1,302 +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 { FORM_DATA_DEFAULTS, NUM_METRIC, SIMPLE_FILTER } from './shared.helper'; - -describe('Visualization > Line', () => { - beforeEach(() => { - cy.intercept('POST', '/superset/explore_json/**').as('getJson'); - }); - - const LINE_CHART_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'line' }; - - it('should show validator error when no metric', () => { - const formData = { ...LINE_CHART_DEFAULTS, metrics: [] }; - cy.visitChartByParams(formData); - cy.get('.panel-body').contains( - `Add required control values to preview chart`, - ); - }); - - it('should not show validator error when metric added', () => { - const formData = { ...LINE_CHART_DEFAULTS, metrics: [] }; - cy.visitChartByParams(formData); - cy.get('.panel-body').contains( - `Add required control values to preview chart`, - ); - cy.get('[data-test="metrics-header"]').contains('Metrics'); - cy.get('[data-test="metrics-header"] [data-test="error-tooltip"]').should( - 'exist', - ); - - cy.get('[data-test=metrics]') - .contains('Drop columns/metrics here or click') - .click(); - - // Title edit for saved metrics is disabled - switch to Simple - cy.get('[id="adhoc-metric-edit-tabs-tab-SIMPLE"]').click(); - - cy.get('input[aria-label="Select column"]').click(); - cy.get('input[aria-label="Select column"]').type('num{enter}'); - cy.get('input[aria-label="Select aggregate options"]').click(); - cy.get('input[aria-label="Select aggregate options"]').type('sum{enter}'); - cy.get('[data-test="AdhocMetricEdit#save"]').contains('Save').click(); - - cy.get('[data-test="metrics-header"]').contains('Metrics'); - cy.get('[data-test="metrics-header"] [data-test="error-tooltip"]').should( - 'not.exist', - ); - - cy.get('.antd5-alert-warning').should('not.exist'); - }); - - it('should allow negative values in Y bounds', () => { - const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] }; - cy.visitChartByParams(formData); - cy.get('#controlSections-tab-display').click(); - cy.get('span').contains('Y Axis Bounds').scrollIntoView(); - cy.get('input[placeholder="Min"]').type('-0.1', { delay: 100 }); - cy.get('.antd5-alert-warning').should('not.exist'); - }); - - it('should allow type to search color schemes and apply the scheme', () => { - cy.get('#controlSections-tab-display').click(); - cy.get('.Control[data-test="color_scheme"]').scrollIntoView(); - cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus(); - cy.focused().type('bnbColors{enter}'); - cy.get( - '.Control[data-test="color_scheme"] .ant-select-selection-item [data-test="bnbColors"]', - ).should('exist'); - cy.get('.line .nv-legend .nv-legend-symbol') - .first() - .should('have.css', 'fill', 'rgb(41, 105, 107)'); - }); - - it('should work with adhoc metric', () => { - const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] }; - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - }); - - it('should work with groupby', () => { - const metrics = ['count']; - const groupby = ['gender']; - const formData = { ...LINE_CHART_DEFAULTS, metrics, groupby }; - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - }); - - it('should work with simple filter', () => { - const metrics = ['count']; - const filters = [SIMPLE_FILTER]; - const formData = { - ...LINE_CHART_DEFAULTS, - metrics, - adhoc_filters: filters, - }; - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - }); - - it('should work with series limit sort asc', () => { - const formData = { - ...LINE_CHART_DEFAULTS, - metrics: [NUM_METRIC], - limit: 10, - groupby: ['name'], - timeseries_limit_metric: NUM_METRIC, - }; - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - }); - - it('should work with series limit sort desc', () => { - const formData = { - ...LINE_CHART_DEFAULTS, - metrics: [NUM_METRIC], - limit: 10, - groupby: ['name'], - timeseries_limit_metric: NUM_METRIC, - order_desc: true, - }; - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - }); - - it('should work with rolling avg', () => { - const metrics = [NUM_METRIC]; - const formData = { - ...LINE_CHART_DEFAULTS, - metrics, - rolling_type: 'mean', - rolling_periods: 10, - }; - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - }); - - it('should work with time shift 1 year', () => { - const metrics = [NUM_METRIC]; - const formData = { - ...LINE_CHART_DEFAULTS, - metrics, - time_compare: ['1 year'], - comparison_type: 'values', - groupby: ['gender'], - }; - - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - - // Offset color should match original line color - cy.get('.nv-legend-text') - .contains('boy') - .siblings() - .first() - .should('have.attr', 'style') - .then(style => { - cy.get('.nv-legend-text') - .contains('boy, 1 year offset') - .siblings() - .first() - .should('have.attr', 'style') - .and('eq', style); - }); - - cy.get('.nv-legend-text') - .contains('girl') - .siblings() - .first() - .should('have.attr', 'style') - .then(style => { - cy.get('.nv-legend-text') - .contains('girl, 1 year offset') - .siblings() - .first() - .should('have.attr', 'style') - .and('eq', style); - }); - }); - - it('should work with time shift yoy', () => { - const metrics = [NUM_METRIC]; - const formData = { - ...LINE_CHART_DEFAULTS, - metrics, - time_compare: ['1 year'], - comparison_type: 'ratio', - }; - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - }); - - it('should work with time shift percentage change', () => { - const metrics = [NUM_METRIC]; - const formData = { - ...LINE_CHART_DEFAULTS, - metrics, - time_compare: ['1 year'], - comparison_type: 'percentage', - }; - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - }); - - it('Test verbose name shows up in legend', () => { - const formData = { - ...LINE_CHART_DEFAULTS, - metrics: ['count'], - }; - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - cy.get('text.nv-legend-text').contains('COUNT(*)'); - }); - - it('Test hidden annotation', () => { - const formData = { - ...LINE_CHART_DEFAULTS, - metrics: ['count'], - annotation_layers: [ - { - name: 'Goal line', - annotationType: 'FORMULA', - sourceType: '', - value: 'y=140000', - overrides: { time_range: null }, - show: false, - showLabel: false, - titleColumn: '', - descriptionColumns: [], - timeColumn: '', - intervalEndColumn: '', - color: null, - opacity: '', - style: 'solid', - width: 1, - showMarkers: false, - hideLine: false, - }, - ], - }; - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - cy.get('.slice_container').within(() => { - // Goal line annotation doesn't show up in legend - cy.get('.nv-legend-text').should('have.length', 1); - }); - }); - - it('Test event annotation time override', () => { - cy.request('/chart/api/read?_flt_3_slice_name=Daily+Totals').then( - response => { - const value = response.body.pks[0]; - const formData = { - ...LINE_CHART_DEFAULTS, - metrics: ['count'], - annotation_layers: [ - { - name: 'Yearly date', - annotationType: 'EVENT', - sourceType: 'table', - value, - overrides: { time_range: null }, - show: true, - showLabel: false, - titleColumn: 'ds', - descriptionColumns: ['ds'], - timeColumn: 'ds', - color: null, - opacity: '', - style: 'solid', - width: 1, - showMarkers: false, - hideLine: false, - }, - ], - }; - cy.visitChartByParams(formData); - }, - ); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - cy.get('.slice_container').within(() => { - cy.get('.nv-event-annotation-layer-0') - .children() - .should('have.length', 44); - }); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/sankey.test.js b/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/sankey.test.js deleted file mode 100644 index e5e6dc2404b83..0000000000000 --- a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/sankey.test.js +++ /dev/null @@ -1,87 +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. - */ -describe('Visualization > Sankey', () => { - beforeEach(() => { - cy.intercept('POST', '/superset/explore_json/**').as('getJson'); - }); - - const SANKEY_FORM_DATA = { - datasource: '1__table', - viz_type: 'sankey', - slice_id: 1, - url_params: {}, - granularity_sqla: null, - time_grain_sqla: 'P1D', - time_range: 'Last week', - groupby: ['source', 'target'], - metric: 'sum__value', - adhoc_filters: [], - row_limit: '5000', - color_scheme: 'bnbColors', - }; - - function verify(formData) { - cy.visitChartByParams(formData); - cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); - } - - it('should work', () => { - verify(SANKEY_FORM_DATA); - cy.get('.chart-container svg g.node rect').should('have.length', 41); - }); - - it('should work with filter', () => { - verify({ - ...SANKEY_FORM_DATA, - adhoc_filters: [ - { - expressionType: 'SQL', - sqlExpression: 'SUM(value) > 0', - clause: 'HAVING', - subject: null, - operator: null, - comparator: null, - filterOptionName: 'filter_jbdwe0hayaj_h9jfer8fy58', - }, - { - expressionType: 'SIMPLE', - subject: 'source', - operator: '==', - comparator: 'Energy', - clause: 'WHERE', - sqlExpression: null, - filterOptionName: 'filter_8e0otka9uif_vmqri4gmbqc', - }, - ], - }); - cy.get('.chart-container svg g.node rect').should('have.length', 6); - }); - - it('should allow type to search color schemes', () => { - verify(SANKEY_FORM_DATA); - - cy.get('#controlSections-tab-display').click(); - cy.get('.Control[data-test="color_scheme"]').scrollIntoView(); - cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus(); - cy.focused().type('bnbColors{enter}'); - cy.get( - '.Control[data-test="color_scheme"] .ant-select-selection-item [data-test="bnbColors"]', - ).should('exist'); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/shared.helper.js b/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/shared.helper.js index bfd50e66d3df2..ca8ae94b70859 100644 --- a/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/shared.helper.js +++ b/superset-frontend/cypress-base/cypress/e2e/explore/visualizations/shared.helper.js @@ -22,15 +22,22 @@ export const FORM_DATA_DEFAULTS = { datasource: '3__table', - granularity_sqla: 'ds', time_grain_sqla: null, - time_range: '100 years ago : now', - adhoc_filters: [], + x_axis: 'ds', + adhoc_filters: [ + { + clause: 'WHERE', + subject: 'ds', + operator: 'TEMPORAL_RANGE', + comparator: '100 years ago : now', + expressionType: 'SIMPLE', + }, + ], groupby: [], limit: null, timeseries_limit_metric: null, order_desc: false, - contribution: false, + contributionMode: null, }; export const HEALTH_POP_FORM_DATA_DEFAULTS = { diff --git a/superset-frontend/cypress-base/cypress/fixtures/charts.json b/superset-frontend/cypress-base/cypress/fixtures/charts.json index 5781fce81e728..879b16b02b3ea 100644 --- a/superset-frontend/cypress-base/cypress/fixtures/charts.json +++ b/superset-frontend/cypress-base/cypress/fixtures/charts.json @@ -3,40 +3,40 @@ "slice_name": "1 - Sample chart", "description": "chart description", "owners": [1], - "viz_type": "line", + "viz_type": "echarts_timeseries_line", "cache_timeout": 1000, "datasource_id": 2, "datasource_type": "table", - "params": "{\"viz_type\":\"line\",\"metrics\":[\"count\"]}" + "params": "{\"viz_type\":\"echarts_timeseries_line\",\"x_axis\":\"year\",\"metrics\":[\"count\"]}" }, { "slice_name": "2 - Sample chart", "description": "chart description", "owners": [1], - "viz_type": "line", + "viz_type": "echarts_timeseries_line", "cache_timeout": 1000, "datasource_id": 2, "datasource_type": "table", - "params": "{\"viz_type\":\"line\",\"metrics\":[\"count\"]}" + "params": "{\"viz_type\":\"echarts_timeseries_line\",\"x_axis\":\"year\",\"metrics\":[\"count\"]}" }, { "slice_name": "3 - Sample chart", "description": "chart description", "owners": [1], - "viz_type": "line", + "viz_type": "echarts_timeseries_line", "cache_timeout": 1000, "datasource_id": 2, "datasource_type": "table", - "params": "{\"viz_type\":\"line\",\"metrics\":[\"count\"]}" + "params": "{\"viz_type\":\"echarts_timeseries_line\",\"x_axis\":\"year\",\"metrics\":[\"count\"]}" }, { "slice_name": "4 - Sample chart", "description": "chart description", "owners": [1], - "viz_type": "line", + "viz_type": "echarts_timeseries_line", "cache_timeout": 1000, "datasource_id": 2, "datasource_type": "table", - "params": "{\"viz_type\":\"line\",\"metrics\":[\"count\"]}" + "params": "{\"viz_type\":\"echarts_timeseries_line\",\"x_axis\":\"year\",\"metrics\":[\"count\"]}" } ] diff --git a/superset-frontend/cypress-base/cypress/support/directories.ts b/superset-frontend/cypress-base/cypress/support/directories.ts index 902c4619ac940..3445cf5f262e5 100644 --- a/superset-frontend/cypress-base/cypress/support/directories.ts +++ b/superset-frontend/cypress-base/cypress/support/directories.ts @@ -25,16 +25,16 @@ export function dataTestChartName(chartName: string): string { export const pageHeader = { logo: '.navbar-brand > img', - headerNavigationItem: '.ant-menu-submenu-title', + headerNavigationItem: '.antd5-menu-submenu-title', headerNavigationDropdown: "[aria-label='triangle-down']", - headerNavigationItemMenu: '.ant-menu-item-group-list', - plusIcon: ':nth-child(2) > .ant-menu-submenu-title', + headerNavigationItemMenu: '.antd5-menu-item-group-list', + plusIcon: ':nth-child(2) > .antd5-menu-submenu-title', plusIconMenuOptions: { sqlQueryOption: dataTestLocator('menu-item-SQL query'), chartOption: dataTestLocator('menu-item-Chart'), dashboardOption: dataTestLocator('menu-item-Dashboard'), }, - plusMenu: '.ant-menu-submenu-popup', + plusMenu: '.antd5-menu-submenu-popup', barButtons: '[role="presentation"]', sqlLabMenu: '[id="item_3$Menu"]', dataMenu: '[id="item_4$Menu"]', @@ -106,7 +106,7 @@ export const databasesPage = { alertMessage: '.antd5-alert-message', errorField: '[role="alert"]', uploadJson: '[title="Upload JSON file"]', - chooseFile: '[class="ant-btn input-upload-btn"]', + chooseFile: '[class="antd5-btn input-upload-btn"]', additionalParameters: '[name="query_input"]', sqlAlchemyUriInput: dataTestLocator('sqlalchemy-uri-input'), advancedTab: '#rc-tabs-0-tab-2', @@ -148,7 +148,7 @@ export const sqlLabView = { examplesMenuItem: '[title="examples"]', tableInput: ':nth-child(4) > .select > :nth-child(1)', sqlEditor: '#brace-editor textarea', - saveAsButton: '.SaveQuery > .ant-btn', + saveAsButton: '.SaveQuery > .antd5-btn', saveAsModal: { footer: '.antd5-modal-footer', queryNameInput: 'input[class^="ant-input"]', @@ -195,7 +195,7 @@ export const savedQuery = { export const annotationLayersView = { emptyDescription: { description: '.ant-empty-description', - addAnnotationLayerButton: '.ant-empty-footer > .ant-btn', + addAnnotationLayerButton: '.ant-empty-footer > .antd5-btn', }, modal: { content: { @@ -434,7 +434,7 @@ export const dashboardListView = { newDashboardButton: '.css-yff34v', }, importModal: { - selectFileButton: '.ant-upload > .ant-btn > span', + selectFileButton: '.ant-upload > .antd5-btn > span', importButton: dataTestLocator('modal-confirm-button'), }, header: { @@ -588,7 +588,7 @@ export const exploreView = { rowsContainer: dataTestLocator('table-content-rows'), }, confirmModal: { - okButton: '.antd5-modal-confirm-btns .ant-btn-primary', + okButton: '.antd5-modal-confirm-btns .antd5-btn-primary', }, }, visualizationTypeModal: { diff --git a/superset-frontend/cypress-base/cypress/support/e2e.ts b/superset-frontend/cypress-base/cypress/support/e2e.ts index 7d17935e36aad..4a471c87d1b05 100644 --- a/superset-frontend/cypress-base/cypress/support/e2e.ts +++ b/superset-frontend/cypress-base/cypress/support/e2e.ts @@ -19,6 +19,7 @@ import '@cypress/code-coverage/support'; import '@applitools/eyes-cypress/commands'; import failOnConsoleError from 'cypress-fail-on-console-error'; +import { expect } from 'chai'; /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -26,6 +27,10 @@ require('cy-verify-downloads').addCustomCommand(); // fail on console error, allow config to override individual tests // these exceptions are a little pile of tech debt +// + +// DISABLING FOR NOW +/* const { getConfig, setConfig } = failOnConsoleError({ consoleMessages: [ /\[webpack-dev-server\]/, @@ -34,7 +39,9 @@ const { getConfig, setConfig } = failOnConsoleError({ 'Error: Unknown Error', /Unable to infer path to ace from script src/, ], + includeConsoleTypes: ['error'], }); +*/ // Set individual tests to allow certain console errors to NOT fail, e.g // cy.allowConsoleErrors(['foo', /^some bar-regex.*/]); @@ -160,7 +167,18 @@ Cypress.Commands.add('login', () => { url: '/login/', body: { username: 'admin', password: 'general' }, }).then(response => { - expect(response.status).to.eq(200); + if (response.status === 302) { + // If there's a redirect, follow it manually + const redirectUrl = response.headers['location']; + cy.request({ + method: 'GET', + url: redirectUrl, + }).then(finalResponse => { + expect(finalResponse.status).to.eq(200); + }); + } else { + expect(response.status).to.eq(200); + } }); }); diff --git a/superset-frontend/cypress-base/package-lock.json b/superset-frontend/cypress-base/package-lock.json index 899b6ad136508..60719f539e254 100644 --- a/superset-frontend/cypress-base/package-lock.json +++ b/superset-frontend/cypress-base/package-lock.json @@ -13,6 +13,7 @@ "@cypress/code-coverage": "^3.10.4", "@superset-ui/core": "^2.1.0", "brace": "^0.11.1", + "chai": "^4.5.0", "cy-verify-downloads": "^0.2.5", "cypress-fail-on-console-error": "^4.0.3", "nanoid": "^5.0.9", @@ -4317,6 +4318,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "license": "MIT", "engines": { "node": "*" } @@ -4790,22 +4792,32 @@ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "license": "MIT", "dependencies": { "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "type-detect": "^4.1.0" }, "engines": { "node": ">=4" } }, + "node_modules/chai/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", @@ -4851,9 +4863,13 @@ } }, "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, "engines": { "node": "*" } @@ -5401,9 +5417,10 @@ } }, "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "license": "MIT", "dependencies": { "type-detect": "^4.0.0" }, @@ -6505,6 +6522,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "license": "MIT", "engines": { "node": "*" } @@ -7739,11 +7757,12 @@ } }, "node_modules/loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "license": "MIT", "dependencies": { - "get-func-name": "^2.0.0" + "get-func-name": "^2.0.1" } }, "node_modules/lowercase-keys": { @@ -8803,6 +8822,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "license": "MIT", "engines": { "node": "*" } @@ -9715,6 +9735,7 @@ "version": "3.7.0", "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz", "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==", + "license": "(BSD-2-Clause OR WTFPL)", "peerDependencies": { "chai": "^4.0.0", "sinon": ">=4.0.0" @@ -14560,17 +14581,24 @@ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, "chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "requires": { "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "type-detect": "^4.1.0" + }, + "dependencies": { + "type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==" + } } }, "chainsaw": { @@ -14607,9 +14635,12 @@ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==" }, "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "requires": { + "get-func-name": "^2.0.2" + } }, "check-more-types": { "version": "2.24.0", @@ -15026,9 +15057,9 @@ } }, "deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "requires": { "type-detect": "^4.0.0" } @@ -16786,11 +16817,11 @@ } }, "loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "requires": { - "get-func-name": "^2.0.0" + "get-func-name": "^2.0.1" } }, "lowercase-keys": { diff --git a/superset-frontend/cypress-base/package.json b/superset-frontend/cypress-base/package.json index e65a346741015..9842d103ba251 100644 --- a/superset-frontend/cypress-base/package.json +++ b/superset-frontend/cypress-base/package.json @@ -20,12 +20,13 @@ "@cypress/code-coverage": "^3.10.4", "@superset-ui/core": "^2.1.0", "brace": "^0.11.1", + "chai": "^4.5.0", "cy-verify-downloads": "^0.2.5", "cypress-fail-on-console-error": "^4.0.3", + "nanoid": "^5.0.9", "querystringify": "^2.2.0", "react-dom": "^16.13.0", - "rison": "^0.1.1", - "nanoid": "^5.0.9" + "rison": "^0.1.1" }, "devDependencies": { "@types/querystringify": "^2.0.0", diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 693c8ac646658..a7ac01fa4143b 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -30,17 +30,12 @@ "@superset-ui/legacy-plugin-chart-calendar": "file:./plugins/legacy-plugin-chart-calendar", "@superset-ui/legacy-plugin-chart-chord": "file:./plugins/legacy-plugin-chart-chord", "@superset-ui/legacy-plugin-chart-country-map": "file:./plugins/legacy-plugin-chart-country-map", - "@superset-ui/legacy-plugin-chart-event-flow": "file:./plugins/legacy-plugin-chart-event-flow", - "@superset-ui/legacy-plugin-chart-heatmap": "file:./plugins/legacy-plugin-chart-heatmap", - "@superset-ui/legacy-plugin-chart-histogram": "file:./plugins/legacy-plugin-chart-histogram", "@superset-ui/legacy-plugin-chart-horizon": "file:./plugins/legacy-plugin-chart-horizon", "@superset-ui/legacy-plugin-chart-map-box": "file:./plugins/legacy-plugin-chart-map-box", "@superset-ui/legacy-plugin-chart-paired-t-test": "file:./plugins/legacy-plugin-chart-paired-t-test", "@superset-ui/legacy-plugin-chart-parallel-coordinates": "file:./plugins/legacy-plugin-chart-parallel-coordinates", "@superset-ui/legacy-plugin-chart-partition": "file:./plugins/legacy-plugin-chart-partition", "@superset-ui/legacy-plugin-chart-rose": "file:./plugins/legacy-plugin-chart-rose", - "@superset-ui/legacy-plugin-chart-sankey": "file:./plugins/legacy-plugin-chart-sankey", - "@superset-ui/legacy-plugin-chart-sankey-loop": "file:./plugins/legacy-plugin-chart-sankey-loop", "@superset-ui/legacy-plugin-chart-world-map": "file:./plugins/legacy-plugin-chart-world-map", "@superset-ui/legacy-preset-chart-deckgl": "file:./plugins/legacy-preset-chart-deckgl", "@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3", @@ -63,7 +58,6 @@ "ace-builds": "^1.36.3", "antd": "4.10.3", "antd-v5": "npm:antd@^5.18.0", - "babel-plugin-typescript-to-proptypes": "^2.0.0", "bootstrap": "^3.4.1", "brace": "^0.11.1", "chrono-node": "^2.7.6", @@ -85,7 +79,6 @@ "geostyler-style": "^7.5.0", "geostyler-wfs-parser": "^2.0.3", "googleapis": "^130.0.0", - "html-webpack-plugin": "^5.6.3", "immer": "^10.1.1", "interweave": "^13.1.0", "jquery": "^3.7.1", @@ -99,8 +92,6 @@ "markdown-to-jsx": "^7.7.2", "match-sorter": "^6.3.4", "memoize-one": "^5.2.1", - "moment": "^2.30.1", - "moment-timezone": "^0.5.44", "mousetrap": "^1.6.5", "mustache": "^4.2.0", "nanoid": "^5.0.9", @@ -229,10 +220,11 @@ "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-jsx-remove-data-test-id": "^3.0.0", "babel-plugin-lodash": "^3.3.4", + "babel-plugin-typescript-to-proptypes": "^2.0.0", "copy-webpack-plugin": "^12.0.2", "cross-env": "^7.0.3", - "css-loader": "^6.8.1", - "css-minimizer-webpack-plugin": "^5.0.1", + "css-loader": "^7.1.2", + "css-minimizer-webpack-plugin": "^7.0.0", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.7", "esbuild": "^0.20.0", @@ -261,6 +253,7 @@ "fetch-mock": "^7.7.3", "fork-ts-checker-webpack-plugin": "^9.0.2", "history": "^5.3.0", + "html-webpack-plugin": "^5.6.3", "ignore-styles": "^5.0.1", "imports-loader": "^5.0.0", "jest": "^29.7.0", @@ -275,8 +268,6 @@ "less-loader": "^12.2.0", "mini-css-extract-plugin": "^2.9.0", "mock-socket": "^9.3.1", - "moment-locales-webpack-plugin": "^1.2.0", - "node-fetch": "^2.6.7", "open-cli": "^8.0.0", "po2json": "^0.4.5", "prettier": "3.3.3", @@ -301,8 +292,7 @@ "webpack-dev-server": "^4.15.1", "webpack-manifest-plugin": "^5.0.0", "webpack-sources": "^3.2.3", - "webpack-visualizer-plugin2": "^1.1.0", - "xdm": "^3.4.0" + "webpack-visualizer-plugin2": "^1.1.0" }, "engines": { "node": "^20.16.0", @@ -325,6 +315,7 @@ }, "node_modules/@ampproject/remapping": { "version": "2.2.0", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.1.0", @@ -336,6 +327,7 @@ }, "node_modules/@ampproject/remapping/node_modules/@jridgewell/gen-mapping": { "version": "0.1.1", + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.0.0", @@ -370,12 +362,14 @@ } }, "node_modules/@ant-design/icons": { - "version": "5.3.7", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.5.2.tgz", + "integrity": "sha512-xc53rjVBl9v2BqFxUjZGti/RfdDeA8/6KYglmInM2PNqSXc/WfuGDTifJI/ZsokJK0aeKvOIbXc9y2g8ILAhEA==", "license": "MIT", "dependencies": { "@ant-design/colors": "^7.0.0", "@ant-design/icons-svg": "^4.4.0", - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.24.8", "classnames": "^2.2.6", "rc-util": "^5.31.1" }, @@ -1299,6 +1293,7 @@ }, "node_modules/@babel/compat-data": { "version": "7.24.7", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1306,6 +1301,7 @@ }, "node_modules/@babel/core": { "version": "7.24.7", + "devOptional": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -1334,10 +1330,12 @@ }, "node_modules/@babel/core/node_modules/convert-source-map": { "version": "2.0.0", + "devOptional": true, "license": "MIT" }, "node_modules/@babel/core/node_modules/debug": { "version": "4.3.1", + "devOptional": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -1353,6 +1351,7 @@ }, "node_modules/@babel/core/node_modules/json5": { "version": "2.2.3", + "devOptional": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -1363,10 +1362,12 @@ }, "node_modules/@babel/core/node_modules/ms": { "version": "2.1.2", + "devOptional": true, "license": "MIT" }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1444,6 +1445,7 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.24.7", + "devOptional": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.24.7", @@ -1458,6 +1460,7 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1609,6 +1612,7 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.24.7", + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-environment-visitor": "^7.24.7", @@ -1637,6 +1641,7 @@ }, "node_modules/@babel/helper-plugin-utils": { "version": "7.24.7", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1676,6 +1681,7 @@ }, "node_modules/@babel/helper-simple-access": { "version": "7.24.7", + "devOptional": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.24.7", @@ -1724,6 +1730,7 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.24.7", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1745,6 +1752,7 @@ }, "node_modules/@babel/helpers": { "version": "7.24.7", + "devOptional": true, "license": "MIT", "dependencies": { "@babel/template": "^7.24.7", @@ -2208,6 +2216,7 @@ }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.23.3", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -3393,7 +3402,9 @@ "license": "MIT" }, "node_modules/@babel/runtime": { - "version": "7.24.7", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -3812,206 +3823,6 @@ "react-dom": "^0.14.9 || ^15.3.0 || ^16.0.0-rc || ^16.0" } }, - "node_modules/@data-ui/histogram": { - "version": "0.0.84", - "license": "MIT", - "dependencies": { - "@data-ui/shared": "^0.0.84", - "@data-ui/theme": "^0.0.84", - "@vx/axis": "^0.0.179", - "@vx/curve": "^0.0.165", - "@vx/event": "^0.0.179", - "@vx/glyph": "^0.0.179", - "@vx/gradient": "^0.0.165", - "@vx/group": "^0.0.170", - "@vx/pattern": "^0.0.179", - "@vx/responsive": "^0.0.192", - "@vx/scale": "^0.0.179", - "@vx/shape": "^0.0.179", - "@vx/tooltip": "0.0.179", - "d3-array": "^1.2.0", - "d3-scale": "^1.0.6", - "prop-types": "^15.5.10", - "react-move": "^2.1.0" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0", - "react-dom": "^15.0.0-0 || ^16.0.0-0" - } - }, - "node_modules/@data-ui/histogram/node_modules/@vx/axis": { - "version": "0.0.179", - "license": "MIT", - "dependencies": { - "@vx/group": "0.0.170", - "@vx/point": "0.0.165", - "@vx/shape": "0.0.179", - "@vx/text": "0.0.179", - "classnames": "^2.2.5", - "prop-types": "^15.6.0" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" - } - }, - "node_modules/@data-ui/histogram/node_modules/@vx/bounds": { - "version": "0.0.165", - "license": "MIT", - "dependencies": { - "prop-types": "^15.5.10" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0", - "react-dom": "^15.0.0-0 || ^16.0.0-0" - } - }, - "node_modules/@data-ui/histogram/node_modules/@vx/event": { - "version": "0.0.179", - "license": "MIT", - "dependencies": { - "@vx/point": "0.0.165" - } - }, - "node_modules/@data-ui/histogram/node_modules/@vx/glyph": { - "version": "0.0.179", - "license": "MIT", - "dependencies": { - "@vx/group": "0.0.170", - "classnames": "^2.2.5", - "d3-shape": "^1.2.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" - } - }, - "node_modules/@data-ui/histogram/node_modules/@vx/gradient": { - "version": "0.0.165", - "license": "MIT", - "dependencies": { - "classnames": "^2.2.5", - "prop-types": "^15.5.7" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" - } - }, - "node_modules/@data-ui/histogram/node_modules/@vx/group": { - "version": "0.0.170", - "license": "MIT", - "dependencies": { - "classnames": "^2.2.5" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" - } - }, - "node_modules/@data-ui/histogram/node_modules/@vx/pattern": { - "version": "0.0.179", - "license": "MIT", - "dependencies": { - "classnames": "^2.2.5", - "prop-types": "^15.5.10" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" - } - }, - "node_modules/@data-ui/histogram/node_modules/@vx/point": { - "version": "0.0.165", - "license": "MIT" - }, - "node_modules/@data-ui/histogram/node_modules/@vx/responsive": { - "version": "0.0.192", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.10", - "prop-types": "^15.6.1", - "resize-observer-polyfill": "1.5.0" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" - } - }, - "node_modules/@data-ui/histogram/node_modules/@vx/scale": { - "version": "0.0.179", - "license": "MIT", - "dependencies": { - "d3-scale": "^2.0.0" - } - }, - "node_modules/@data-ui/histogram/node_modules/@vx/scale/node_modules/d3-scale": { - "version": "2.2.2", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "^1.2.0", - "d3-collection": "1", - "d3-format": "1", - "d3-interpolate": "1", - "d3-time": "1", - "d3-time-format": "2" - } - }, - "node_modules/@data-ui/histogram/node_modules/@vx/shape": { - "version": "0.0.179", - "license": "MIT", - "dependencies": { - "@vx/curve": "0.0.165", - "@vx/group": "0.0.170", - "@vx/point": "0.0.165", - "classnames": "^2.2.5", - "d3-path": "^1.0.5", - "d3-shape": "^1.2.0", - "prop-types": "^15.5.10" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" - } - }, - "node_modules/@data-ui/histogram/node_modules/@vx/tooltip": { - "version": "0.0.179", - "license": "MIT", - "dependencies": { - "@vx/bounds": "0.0.165", - "classnames": "^2.2.5", - "prop-types": "^15.5.10" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" - } - }, - "node_modules/@data-ui/histogram/node_modules/d3-interpolate": { - "version": "1.4.0", - "license": "BSD-3-Clause", - "dependencies": { - "d3-color": "1" - } - }, - "node_modules/@data-ui/histogram/node_modules/d3-scale": { - "version": "1.0.7", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "^1.2.0", - "d3-collection": "1", - "d3-color": "1", - "d3-format": "1", - "d3-interpolate": "1", - "d3-time": "1", - "d3-time-format": "2" - } - }, - "node_modules/@data-ui/histogram/node_modules/d3-time-format": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", - "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", - "dependencies": { - "d3-time": "1" - } - }, - "node_modules/@data-ui/histogram/node_modules/resize-observer-polyfill": { - "version": "1.5.0", - "license": "MIT" - }, "node_modules/@data-ui/radial-chart": { "version": "0.0.84", "license": "MIT", @@ -6478,6 +6289,7 @@ }, "node_modules/@jridgewell/source-map": { "version": "0.3.5", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", @@ -9648,23 +9460,6 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, - "node_modules/@rollup/pluginutils": { - "version": "4.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, "node_modules/@scarf/scarf": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", @@ -10226,6 +10021,41 @@ "undici-types": "~5.26.4" } }, + "node_modules/@storybook/builder-webpack5/node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, "node_modules/@storybook/builder-webpack5/node_modules/deepmerge": { "version": "4.3.1", "dev": true, @@ -12363,18 +12193,6 @@ "resolved": "plugins/legacy-plugin-chart-country-map", "link": true }, - "node_modules/@superset-ui/legacy-plugin-chart-event-flow": { - "resolved": "plugins/legacy-plugin-chart-event-flow", - "link": true - }, - "node_modules/@superset-ui/legacy-plugin-chart-heatmap": { - "resolved": "plugins/legacy-plugin-chart-heatmap", - "link": true - }, - "node_modules/@superset-ui/legacy-plugin-chart-histogram": { - "resolved": "plugins/legacy-plugin-chart-histogram", - "link": true - }, "node_modules/@superset-ui/legacy-plugin-chart-horizon": { "resolved": "plugins/legacy-plugin-chart-horizon", "link": true @@ -12399,14 +12217,6 @@ "resolved": "plugins/legacy-plugin-chart-rose", "link": true }, - "node_modules/@superset-ui/legacy-plugin-chart-sankey": { - "resolved": "plugins/legacy-plugin-chart-sankey", - "link": true - }, - "node_modules/@superset-ui/legacy-plugin-chart-sankey-loop": { - "resolved": "plugins/legacy-plugin-chart-sankey-loop", - "link": true - }, "node_modules/@superset-ui/legacy-plugin-chart-time-table": { "version": "0.18.25", "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-time-table/-/legacy-plugin-chart-time-table-0.18.25.tgz", @@ -13234,14 +13044,6 @@ "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "dev": true }, - "node_modules/@types/acorn": { - "version": "4.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -13519,7 +13321,7 @@ "version": "8.56.11", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.11.tgz", "integrity": "sha512-sVBpJMf7UPo/wGecYOpk2aQya2VUGeHhe38WG7/mN5FufNSubf5VT9Uh9Uyp8/eLJpu1/tuhJ/qTo4mhSB4V4Q==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "*", @@ -13530,7 +13332,7 @@ "version": "3.7.7", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "devOptional": true, + "dev": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -13540,15 +13342,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "devOptional": true - }, - "node_modules/@types/estree-jsx": { - "version": "0.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } + "dev": true }, "node_modules/@types/expect": { "version": "1.20.4", @@ -13652,6 +13446,7 @@ }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", + "dev": true, "license": "MIT" }, "node_modules/@types/http-cache-semantics": { @@ -15322,69 +15117,6 @@ "react": "^15.0.0-0 || ^16.0.0-0" } }, - "node_modules/@vx/scale": { - "version": "0.0.199", - "license": "MIT", - "dependencies": { - "@types/d3-interpolate": "^1.3.1", - "@types/d3-scale": "^2.1.1", - "@types/d3-time": "^1.0.10", - "d3-interpolate": "^1.4.0", - "d3-scale": "^3.0.1", - "d3-time": "^1.1.0" - } - }, - "node_modules/@vx/scale/node_modules/@types/d3-interpolate": { - "version": "1.4.5", - "license": "MIT", - "dependencies": { - "@types/d3-color": "^1" - } - }, - "node_modules/@vx/scale/node_modules/d3-array": { - "version": "2.12.1", - "license": "BSD-3-Clause", - "dependencies": { - "internmap": "^1.0.0" - } - }, - "node_modules/@vx/scale/node_modules/d3-interpolate": { - "version": "1.4.0", - "license": "BSD-3-Clause", - "dependencies": { - "d3-color": "1" - } - }, - "node_modules/@vx/scale/node_modules/d3-scale": { - "version": "3.3.0", - "license": "BSD-3-Clause", - "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/@vx/scale/node_modules/d3-scale/node_modules/d3-time": { - "version": "2.1.1", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "2" - } - }, - "node_modules/@vx/scale/node_modules/d3-time": { - "version": "1.1.0", - "license": "BSD-3-Clause" - }, - "node_modules/@vx/scale/node_modules/d3-time-format": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", - "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", - "dependencies": { - "d3-time": "1 - 2" - } - }, "node_modules/@vx/shape": { "version": "0.0.140", "license": "MIT", @@ -15440,6 +15172,7 @@ "node_modules/@vx/text": { "version": "0.0.179", "license": "MIT", + "peer": true, "dependencies": { "babel-plugin-lodash": "^3.3.2", "classnames": "^2.2.5", @@ -15675,7 +15408,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -15685,25 +15418,25 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "devOptional": true + "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "devOptional": true + "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "devOptional": true + "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -15714,13 +15447,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "devOptional": true + "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -15732,7 +15465,7 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "devOptional": true, + "dev": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -15741,7 +15474,7 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "devOptional": true, + "dev": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -15750,13 +15483,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "devOptional": true + "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -15772,7 +15505,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -15785,7 +15518,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -15797,7 +15530,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -15811,7 +15544,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -15863,13 +15596,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "devOptional": true + "dev": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "devOptional": true + "dev": true }, "node_modules/@yarnpkg/esbuild-plugin-pnp": { "version": "3.0.0-rc.15", @@ -16242,7 +15975,7 @@ }, "node_modules/ajv": { "version": "6.12.6", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -16290,7 +16023,7 @@ }, "node_modules/ajv-keywords": { "version": "3.5.2", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" @@ -16647,6 +16380,45 @@ "react-dom": ">=16.9.0" } }, + "node_modules/antd-v5/node_modules/rc-picker": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.5.0.tgz", + "integrity": "sha512-suqz9bzuhBQlf7u+bZd1bJLPzhXpk12w6AjQ9BTPTiFwexVZgUKViG1KNLyfFvW6tCUZZK0HmCCX7JAyM+JnCg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.38.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, "node_modules/antd-v5/node_modules/rc-progress": { "version": "4.0.0", "license": "MIT", @@ -16775,13 +16547,16 @@ } }, "node_modules/antd/node_modules/@ant-design/icons": { - "version": "4.8.0", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.8.3.tgz", + "integrity": "sha512-HGlIQZzrEbAhpJR6+IGdzfbPym94Owr6JZkJ2QCCnOkPVIWMO2xgIVcOKnl8YcpijIo39V7l2qQL5fmtw56cMw==", "license": "MIT", "dependencies": { "@ant-design/colors": "^6.0.0", - "@ant-design/icons-svg": "^4.2.1", + "@ant-design/icons-svg": "^4.3.0", "@babel/runtime": "^7.11.2", "classnames": "^2.2.6", + "lodash": "^4.17.15", "rc-util": "^5.9.4" }, "engines": { @@ -17420,14 +17195,6 @@ "node": ">=8" } }, - "node_modules/astring": { - "version": "1.8.6", - "dev": true, - "license": "MIT", - "bin": { - "astring": "bin/astring" - } - }, "node_modules/async": { "version": "3.2.4", "license": "MIT" @@ -17979,6 +17746,7 @@ }, "node_modules/babel-plugin-typescript-to-proptypes": { "version": "2.1.0", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.15.4", @@ -18539,6 +18307,7 @@ }, "node_modules/boolbase": { "version": "1.0.0", + "dev": true, "license": "ISC" }, "node_modules/bootstrap": { @@ -18633,6 +18402,7 @@ "version": "4.24.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", + "devOptional": true, "funding": [ { "type": "opencollective", @@ -19104,6 +18874,7 @@ }, "node_modules/camel-case": { "version": "4.1.2", + "dev": true, "license": "MIT", "dependencies": { "pascal-case": "^3.1.2", @@ -19112,6 +18883,7 @@ }, "node_modules/camel-case/node_modules/tslib": { "version": "2.6.2", + "dev": true, "license": "0BSD" }, "node_modules/camelcase": { @@ -19141,8 +18913,9 @@ }, "node_modules/caniuse-api": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", "dev": true, - "license": "MIT", "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", @@ -19154,6 +18927,7 @@ "version": "1.0.30001689", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz", "integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==", + "devOptional": true, "funding": [ { "type": "opencollective", @@ -19284,15 +19058,6 @@ "version": "1.2.2", "license": "MIT" }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/character-entities-legacy": { "version": "1.1.2", "license": "MIT" @@ -19474,7 +19239,7 @@ }, "node_modules/chrome-trace-event": { "version": "1.0.2", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "tslib": "^1.9.0" @@ -19567,6 +19332,7 @@ }, "node_modules/clean-css": { "version": "5.3.3", + "dev": true, "license": "MIT", "dependencies": { "source-map": "~0.6.0" @@ -19577,6 +19343,7 @@ }, "node_modules/clean-css/node_modules/source-map": { "version": "0.6.1", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -19827,8 +19594,9 @@ }, "node_modules/colord": { "version": "2.9.3", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true }, "node_modules/colorette": { "version": "1.4.0", @@ -20738,9 +20506,10 @@ } }, "node_modules/css-declaration-sorter": { - "version": "7.1.1", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", + "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", "dev": true, - "license": "ISC", "engines": { "node": "^14 || ^16 || >=18" }, @@ -20773,28 +20542,38 @@ } }, "node_modules/css-loader": { - "version": "6.8.1", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", "dev": true, - "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.21", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.3", - "postcss-modules-scope": "^3.0.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^5.0.0" + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/css-loader/node_modules/lru-cache": { @@ -20828,19 +20607,20 @@ "license": "ISC" }, "node_modules/css-minimizer-webpack-plugin": { - "version": "5.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-7.0.0.tgz", + "integrity": "sha512-niy66jxsQHqO+EYbhPuIhqRQ1mNcNVUHrMnkzzir9kFOERJUaQDDRhh7dKDz33kBpkWMF9M8Vx0QlDbc5AHOsw==", "dev": true, - "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "cssnano": "^6.0.1", - "jest-worker": "^29.4.3", - "postcss": "^8.4.24", - "schema-utils": "^4.0.1", - "serialize-javascript": "^6.0.1" + "@jridgewell/trace-mapping": "^0.3.25", + "cssnano": "^7.0.1", + "jest-worker": "^29.7.0", + "postcss": "^8.4.38", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -20936,6 +20716,7 @@ }, "node_modules/css-what": { "version": "6.1.0", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">= 6" @@ -20968,15 +20749,16 @@ "license": "MIT" }, "node_modules/cssnano": { - "version": "6.0.3", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.6.tgz", + "integrity": "sha512-54woqx8SCbp8HwvNZYn68ZFAepuouZW4lTwiMVnBErM3VkO7/Sd4oTOt3Zz3bPx3kxQ36aISppyXj2Md4lg8bw==", "dev": true, - "license": "MIT", "dependencies": { - "cssnano-preset-default": "^6.0.3", - "lilconfig": "^3.0.0" + "cssnano-preset-default": "^7.0.6", + "lilconfig": "^3.1.2" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "funding": { "type": "opencollective", @@ -20987,53 +20769,56 @@ } }, "node_modules/cssnano-preset-default": { - "version": "6.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "css-declaration-sorter": "^7.1.1", - "cssnano-utils": "^4.0.1", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.0.2", - "postcss-convert-values": "^6.0.2", - "postcss-discard-comments": "^6.0.1", - "postcss-discard-duplicates": "^6.0.1", - "postcss-discard-empty": "^6.0.1", - "postcss-discard-overridden": "^6.0.1", - "postcss-merge-longhand": "^6.0.2", - "postcss-merge-rules": "^6.0.3", - "postcss-minify-font-values": "^6.0.1", - "postcss-minify-gradients": "^6.0.1", - "postcss-minify-params": "^6.0.2", - "postcss-minify-selectors": "^6.0.2", - "postcss-normalize-charset": "^6.0.1", - "postcss-normalize-display-values": "^6.0.1", - "postcss-normalize-positions": "^6.0.1", - "postcss-normalize-repeat-style": "^6.0.1", - "postcss-normalize-string": "^6.0.1", - "postcss-normalize-timing-functions": "^6.0.1", - "postcss-normalize-unicode": "^6.0.2", - "postcss-normalize-url": "^6.0.1", - "postcss-normalize-whitespace": "^6.0.1", - "postcss-ordered-values": "^6.0.1", - "postcss-reduce-initial": "^6.0.2", - "postcss-reduce-transforms": "^6.0.1", - "postcss-svgo": "^6.0.2", - "postcss-unique-selectors": "^6.0.2" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.6.tgz", + "integrity": "sha512-ZzrgYupYxEvdGGuqL+JKOY70s7+saoNlHSCK/OGn1vB2pQK8KSET8jvenzItcY+kA7NoWvfbb/YhlzuzNKjOhQ==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.3", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^5.0.0", + "postcss-calc": "^10.0.2", + "postcss-colormin": "^7.0.2", + "postcss-convert-values": "^7.0.4", + "postcss-discard-comments": "^7.0.3", + "postcss-discard-duplicates": "^7.0.1", + "postcss-discard-empty": "^7.0.0", + "postcss-discard-overridden": "^7.0.0", + "postcss-merge-longhand": "^7.0.4", + "postcss-merge-rules": "^7.0.4", + "postcss-minify-font-values": "^7.0.0", + "postcss-minify-gradients": "^7.0.0", + "postcss-minify-params": "^7.0.2", + "postcss-minify-selectors": "^7.0.4", + "postcss-normalize-charset": "^7.0.0", + "postcss-normalize-display-values": "^7.0.0", + "postcss-normalize-positions": "^7.0.0", + "postcss-normalize-repeat-style": "^7.0.0", + "postcss-normalize-string": "^7.0.0", + "postcss-normalize-timing-functions": "^7.0.0", + "postcss-normalize-unicode": "^7.0.2", + "postcss-normalize-url": "^7.0.0", + "postcss-normalize-whitespace": "^7.0.0", + "postcss-ordered-values": "^7.0.1", + "postcss-reduce-initial": "^7.0.2", + "postcss-reduce-transforms": "^7.0.0", + "postcss-svgo": "^7.0.1", + "postcss-unique-selectors": "^7.0.3" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/cssnano-utils": { - "version": "4.0.1", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.0.tgz", + "integrity": "sha512-Uij0Xdxc24L6SirFr25MlwC2rCFX6scyUmuKpzI+JQ7cyqDEwD42fJ0xfB3yLfOnRDU5LKGgjQ9FA6LYh76GWQ==", "dev": true, - "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" @@ -21491,47 +21276,6 @@ "version": "2.0.3", "license": "BSD-3-Clause" }, - "node_modules/d3-sankey": { - "version": "0.4.2", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "1", - "d3-collection": "1", - "d3-interpolate": "1" - } - }, - "node_modules/d3-sankey-diagram": { - "version": "0.7.3", - "license": "MIT", - "dependencies": { - "d3-array": "^1.0.2", - "d3-collection": "^1.0.2", - "d3-dispatch": "^1.0.3", - "d3-format": "^1.1.1", - "d3-interpolate": "^1.1.3", - "d3-selection": "^1.0.3", - "d3-transition": "^1.0.4", - "graphlib": "~2.1.0" - } - }, - "node_modules/d3-sankey-diagram/node_modules/d3-interpolate": { - "version": "1.4.0", - "license": "BSD-3-Clause", - "dependencies": { - "d3-color": "1" - } - }, - "node_modules/d3-sankey-diagram/node_modules/d3-selection": { - "version": "1.4.2", - "license": "BSD-3-Clause" - }, - "node_modules/d3-sankey/node_modules/d3-interpolate": { - "version": "1.4.0", - "license": "BSD-3-Clause", - "dependencies": { - "d3-color": "1" - } - }, "node_modules/d3-scale": { "version": "2.2.2", "license": "BSD-3-Clause", @@ -21588,13 +21332,6 @@ "d3-path": "1" } }, - "node_modules/d3-svg-legend": { - "version": "1.13.0", - "license": "ISC", - "peerDependencies": { - "d3": "^3.0.0" - } - }, "node_modules/d3-time": { "version": "1.0.10", "license": "BSD-3-Clause" @@ -21848,26 +21585,6 @@ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" }, - "node_modules/deasync": { - "version": "0.1.29", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "bindings": "^1.5.0", - "node-addon-api": "^1.7.1" - }, - "engines": { - "node": ">=0.11.0" - } - }, - "node_modules/deasync/node_modules/node-addon-api": { - "version": "1.7.2", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/debounce": { "version": "1.2.1", "license": "MIT" @@ -22771,6 +22488,7 @@ }, "node_modules/dom-converter": { "version": "0.2.0", + "dev": true, "license": "MIT", "dependencies": { "utila": "~0.4" @@ -22820,6 +22538,7 @@ }, "node_modules/domelementtype": { "version": "2.3.0", + "dev": true, "funding": [ { "type": "github", @@ -22864,6 +22583,7 @@ }, "node_modules/dot-case": { "version": "3.0.4", + "dev": true, "license": "MIT", "dependencies": { "no-case": "^3.0.4", @@ -22872,6 +22592,7 @@ }, "node_modules/dot-case/node_modules/tslib": { "version": "2.1.0", + "dev": true, "license": "0BSD" }, "node_modules/dot-prop": { @@ -22990,7 +22711,8 @@ "node_modules/electron-to-chromium": { "version": "1.5.77", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.77.tgz", - "integrity": "sha512-AnJSrt5JpRVgY6dgd5yccguLc5A7oMSF0Kt3fcW+Hp5WTuFbl5upeSFZbMZYy2o7jhmIhU8Ekrd82GhyXUqUUg==" + "integrity": "sha512-AnJSrt5JpRVgY6dgd5yccguLc5A7oMSF0Kt3fcW+Hp5WTuFbl5upeSFZbMZYy2o7jhmIhU8Ekrd82GhyXUqUUg==", + "devOptional": true }, "node_modules/email-addresses": { "version": "5.0.0", @@ -23491,7 +23213,7 @@ }, "node_modules/es-module-lexer": { "version": "1.5.4", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { @@ -24763,7 +24485,7 @@ }, "node_modules/eslint-scope": { "version": "5.1.1", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -25116,7 +24838,7 @@ }, "node_modules/esrecurse": { "version": "4.3.0", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -25127,7 +24849,7 @@ }, "node_modules/esrecurse/node_modules/estraverse": { "version": "5.2.0", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -25140,78 +24862,6 @@ "node": ">=0.10.0" } }, - "node_modules/estree-util-attach-comments": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-build-jsx": { - "version": "2.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "estree-util-is-identifier-name": "^2.0.0", - "estree-walker": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-build-jsx/node_modules/@types/estree-jsx": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/estree-util-is-identifier-name": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-visit": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-visit/node_modules/@types/estree-jsx": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/esutils": { "version": "2.0.2", "engines": { @@ -25811,7 +25461,7 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/fast-json-stringify": { @@ -27036,6 +26686,7 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -27927,7 +27578,7 @@ }, "node_modules/glob-to-regexp": { "version": "0.4.1", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause" }, "node_modules/global": { @@ -28135,13 +27786,6 @@ "dev": true, "license": "MIT" }, - "node_modules/graphlib": { - "version": "2.1.8", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.15" - } - }, "node_modules/grid-index": { "version": "1.1.0", "license": "ISC" @@ -28733,67 +28377,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-to-estree": { - "version": "2.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^2.0.0", - "@types/unist": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "estree-util-attach-comments": "^2.0.0", - "estree-util-is-identifier-name": "^2.0.0", - "hast-util-whitespace": "^2.0.0", - "mdast-util-mdx-expression": "^1.0.0", - "mdast-util-mdxjs-esm": "^1.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-object": "^0.4.1", - "unist-util-position": "^4.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-estree/node_modules/@types/estree-jsx": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/hast-util-to-estree/node_modules/comma-separated-tokens": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hast-util-to-estree/node_modules/property-information": { - "version": "6.4.1", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hast-util-to-estree/node_modules/space-separated-tokens": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/hast-util-to-parse5": { "version": "8.0.0", "license": "MIT", @@ -28887,6 +28470,7 @@ }, "node_modules/he": { "version": "1.2.0", + "dev": true, "license": "MIT", "bin": { "he": "bin/he" @@ -28998,6 +28582,7 @@ }, "node_modules/html-minifier-terser": { "version": "6.1.0", + "dev": true, "license": "MIT", "dependencies": { "camel-case": "^4.1.2", @@ -29017,6 +28602,7 @@ }, "node_modules/html-minifier-terser/node_modules/commander": { "version": "8.3.0", + "dev": true, "license": "MIT", "engines": { "node": ">= 12" @@ -29044,6 +28630,7 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", + "dev": true, "dependencies": { "@types/html-minifier-terser": "^6.0.0", "html-minifier-terser": "^6.0.2", @@ -29073,6 +28660,7 @@ }, "node_modules/html-webpack-plugin/node_modules/tapable": { "version": "2.2.1", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -30346,14 +29934,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-reference": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, "node_modules/is-regex": { "version": "1.1.4", "license": "MIT", @@ -33639,7 +33219,7 @@ }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -34774,11 +34354,15 @@ } }, "node_modules/lilconfig": { - "version": "3.0.0", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, - "license": "MIT", "engines": { "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { @@ -34839,7 +34423,7 @@ }, "node_modules/loader-runner": { "version": "4.2.0", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" @@ -34907,13 +34491,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.escape": { "version": "4.0.1", "dev": true, @@ -34970,8 +34547,9 @@ }, "node_modules/lodash.memoize": { "version": "4.1.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -34991,8 +34569,9 @@ }, "node_modules/lodash.uniq": { "version": "4.5.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -35066,6 +34645,7 @@ }, "node_modules/lower-case": { "version": "2.0.2", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.3" @@ -35073,6 +34653,7 @@ }, "node_modules/lower-case/node_modules/tslib": { "version": "2.1.0", + "dev": true, "license": "0BSD" }, "node_modules/lowercase-keys": { @@ -35098,6 +34679,7 @@ }, "node_modules/lru-cache": { "version": "5.1.1", + "devOptional": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -35264,14 +34846,6 @@ "integrity": "sha512-VJ6nB8emkO9VODI0Fk+TQ/0zKBTqmf/Pkt8Xv0kHstoc0iXRajA00DAid4Kc3K5xeFIOoiZrVxijEzj0GLVO2w==", "license": "BSD-2-Clause" }, - "node_modules/markdown-extensions": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/markdown-table": { "version": "3.0.3", "license": "MIT", @@ -38003,277 +37577,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-mdx": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^1.0.0", - "mdast-util-mdx-expression": "^1.0.0", - "mdast-util-mdx-jsx": "^2.0.0", - "mdast-util-mdxjs-esm": "^1.0.0", - "mdast-util-to-markdown": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-expression": { - "version": "1.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^2.0.0", - "@types/mdast": "^3.0.0", - "mdast-util-from-markdown": "^1.0.0", - "mdast-util-to-markdown": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-expression/node_modules/@types/estree-jsx": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-phrasing": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "unist-util-is": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-to-markdown": { - "version": "1.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^3.0.0", - "mdast-util-to-string": "^3.0.0", - "micromark-util-decode-string": "^1.0.0", - "unist-util-visit": "^4.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-to-string": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-jsx": { - "version": "2.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^2.0.0", - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "ccount": "^2.0.0", - "mdast-util-from-markdown": "^1.1.0", - "mdast-util-to-markdown": "^1.3.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-remove-position": "^4.0.0", - "unist-util-stringify-position": "^3.0.0", - "vfile-message": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-jsx/node_modules/@types/estree-jsx": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-phrasing": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "unist-util-is": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-to-markdown": { - "version": "1.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^3.0.0", - "mdast-util-to-string": "^3.0.0", - "micromark-util-decode-string": "^1.0.0", - "unist-util-visit": "^4.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-to-string": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx/node_modules/mdast-util-phrasing": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "unist-util-is": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx/node_modules/mdast-util-to-markdown": { - "version": "1.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^3.0.0", - "mdast-util-to-string": "^3.0.0", - "micromark-util-decode-string": "^1.0.0", - "unist-util-visit": "^4.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx/node_modules/mdast-util-to-string": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdxjs-esm": { - "version": "1.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^2.0.0", - "@types/mdast": "^3.0.0", - "mdast-util-from-markdown": "^1.0.0", - "mdast-util-to-markdown": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdxjs-esm/node_modules/@types/estree-jsx": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-phrasing": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "unist-util-is": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-to-markdown": { - "version": "1.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^3.0.0", - "mdast-util-to-string": "^3.0.0", - "micromark-util-decode-string": "^1.0.0", - "unist-util-visit": "^4.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-to-string": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdast-util-phrasing": { "version": "4.1.0", "dev": true, @@ -39784,103 +39087,6 @@ ], "license": "MIT" }, - "node_modules/micromark-extension-mdx-expression": { - "version": "1.0.8", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "micromark-factory-mdx-expression": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-events-to-acorn": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-extension-mdx-jsx": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/acorn": "^4.0.0", - "@types/estree": "^1.0.0", - "estree-util-is-identifier-name": "^2.0.0", - "micromark-factory-mdx-expression": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0", - "vfile-message": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdx-md": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark-util-types": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.0.0", - "acorn-jsx": "^5.0.0", - "micromark-extension-mdx-expression": "^1.0.0", - "micromark-extension-mdx-jsx": "^1.0.0", - "micromark-extension-mdx-md": "^1.0.0", - "micromark-extension-mdxjs-esm": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", - "micromark-util-types": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs-esm": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "micromark-core-commonmark": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-events-to-acorn": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "unist-util-position-from-estree": "^1.1.0", - "uvu": "^0.5.0", - "vfile-message": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/micromark-factory-destination": { "version": "1.0.0", "funding": [ @@ -39920,31 +39126,6 @@ "uvu": "^0.5.0" } }, - "node_modules/micromark-factory-mdx-expression": { - "version": "1.0.9", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-events-to-acorn": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "unist-util-position-from-estree": "^1.0.0", - "uvu": "^0.5.0", - "vfile-message": "^3.0.0" - } - }, "node_modules/micromark-factory-space": { "version": "1.0.0", "funding": [ @@ -40127,31 +39308,6 @@ ], "license": "MIT" }, - "node_modules/micromark-util-events-to-acorn": { - "version": "1.2.3", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/acorn": "^4.0.0", - "@types/estree": "^1.0.0", - "@types/unist": "^2.0.0", - "estree-util-visit": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0", - "vfile-message": "^3.0.0" - } - }, "node_modules/micromark-util-html-tag-name": { "version": "1.1.0", "funding": [ @@ -40713,30 +39869,6 @@ "node": "*" } }, - "node_modules/moment-locales-webpack-plugin": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/moment-locales-webpack-plugin/-/moment-locales-webpack-plugin-1.2.0.tgz", - "integrity": "sha512-QAi5v0OlPUP7GXviKMtxnpBAo8WmTHrUNN7iciAhNOEAd9evCOvuN0g1N7ThIg3q11GLCkjY1zQ2saRcf/43nQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.difference": "^4.5.0" - }, - "peerDependencies": { - "moment": "^2.8.0", - "webpack": "^1 || ^2 || ^3 || ^4 || ^5" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.44", - "license": "MIT", - "dependencies": { - "moment": "^2.29.4" - }, - "engines": { - "node": "*" - } - }, "node_modules/monaco-editor": { "version": "0.34.1", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.34.1.tgz", @@ -41015,6 +40147,7 @@ }, "node_modules/no-case": { "version": "3.0.4", + "dev": true, "license": "MIT", "dependencies": { "lower-case": "^2.0.2", @@ -41023,6 +40156,7 @@ }, "node_modules/no-case/node_modules/tslib": { "version": "2.1.0", + "dev": true, "license": "0BSD" }, "node_modules/node-abort-controller": { @@ -41425,7 +40559,8 @@ "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "devOptional": true }, "node_modules/nomnom": { "version": "1.8.1", @@ -41685,6 +40820,7 @@ }, "node_modules/nth-check": { "version": "2.1.1", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" @@ -43317,6 +42453,7 @@ }, "node_modules/param-case": { "version": "3.0.4", + "dev": true, "license": "MIT", "dependencies": { "dot-case": "^3.0.4", @@ -43325,6 +42462,7 @@ }, "node_modules/param-case/node_modules/tslib": { "version": "2.6.2", + "dev": true, "license": "0BSD" }, "node_modules/parent-module": { @@ -43360,92 +42498,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/parse-entities": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "character-entities": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/character-entities": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/character-entities-legacy": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/character-reference-invalid": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/is-alphabetical": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/is-alphanumerical": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/is-decimal": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/is-hexadecimal": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/parse-headers": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", @@ -43541,6 +42593,7 @@ }, "node_modules/pascal-case": { "version": "3.1.2", + "dev": true, "license": "MIT", "dependencies": { "no-case": "^3.0.4", @@ -43549,6 +42602,7 @@ }, "node_modules/pascal-case/node_modules/tslib": { "version": "2.6.2", + "dev": true, "license": "0BSD" }, "node_modules/pascalcase": { @@ -43689,16 +42743,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/periscopic": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -43990,7 +43034,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "dev": true, "funding": [ { @@ -44006,203 +43052,220 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-calc": { - "version": "9.0.1", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.0.2.tgz", + "integrity": "sha512-DT/Wwm6fCKgpYVI7ZEWuPJ4az8hiEHtCUeYjZXqU7Ou4QqYh1Df2yCQ7Ca6N7xqKPFkxN3fhf+u9KSoOCJNAjg==", "dev": true, - "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.11", + "postcss-selector-parser": "^6.1.2", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12 || ^20.9 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.2" + "postcss": "^8.4.38" } }, "node_modules/postcss-colormin": { - "version": "6.0.2", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.2.tgz", + "integrity": "sha512-YntRXNngcvEvDbEjTdRWGU606eZvB5prmHG4BF0yLmVpamXbpsRJzevyy6MZVyuecgzI2AWAlvFi8DAeCqwpvA==", "dev": true, - "license": "MIT", "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.3", "caniuse-api": "^3.0.0", - "colord": "^2.9.1", + "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-convert-values": { - "version": "6.0.2", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.4.tgz", + "integrity": "sha512-e2LSXPqEHVW6aoGbjV9RsSSNDO3A0rZLCBxN24zvxF25WknMPpX8Dm9UxxThyEbaytzggRuZxaGXqaOhxQ514Q==", "dev": true, - "license": "MIT", "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.3", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-discard-comments": { - "version": "6.0.1", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.3.tgz", + "integrity": "sha512-q6fjd4WU4afNhWOA2WltHgCbkRhZPgQe7cXF74fuVB/ge4QbM9HEaOIzGSiMvM+g/cOsNAUGdf2JDzqA2F8iLA==", "dev": true, - "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.2" + }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-discard-duplicates": { - "version": "6.0.1", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.1.tgz", + "integrity": "sha512-oZA+v8Jkpu1ct/xbbrntHRsfLGuzoP+cpt0nJe5ED2FQF8n8bJtn7Bo28jSmBYwqgqnqkuSXJfSUEE7if4nClQ==", "dev": true, - "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-discard-empty": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.0.tgz", + "integrity": "sha512-e+QzoReTZ8IAwhnSdp/++7gBZ/F+nBq9y6PomfwORfP7q9nBpK5AMP64kOt0bA+lShBFbBDcgpJ3X4etHg4lzA==", "dev": true, - "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-discard-overridden": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.0.tgz", + "integrity": "sha512-GmNAzx88u3k2+sBTZrJSDauR0ccpE24omTQCVmaTTZFz1du6AasspjaUPMJ2ud4RslZpoFKyf+6MSPETLojc6w==", "dev": true, - "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-merge-longhand": { - "version": "6.0.2", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.4.tgz", + "integrity": "sha512-zer1KoZA54Q8RVHKOY5vMke0cCdNxMP3KBfDerjH/BYHh4nCIh+1Yy0t1pAEQF18ac/4z3OFclO+ZVH8azjR4A==", "dev": true, - "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.0.2" + "stylehacks": "^7.0.4" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-merge-rules": { - "version": "6.0.3", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.4.tgz", + "integrity": "sha512-ZsaamiMVu7uBYsIdGtKJ64PkcQt6Pcpep/uO90EpLS3dxJi6OXamIobTYcImyXGoW0Wpugh7DSD3XzxZS9JCPg==", "dev": true, - "license": "MIT", "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.3", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.1", - "postcss-selector-parser": "^6.0.15" + "cssnano-utils": "^5.0.0", + "postcss-selector-parser": "^6.1.2" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-minify-font-values": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.0.tgz", + "integrity": "sha512-2ckkZtgT0zG8SMc5aoNwtm5234eUx1GGFJKf2b1bSp8UflqaeFzR50lid4PfqVI9NtGqJ2J4Y7fwvnP/u1cQog==", "dev": true, - "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-minify-gradients": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.0.tgz", + "integrity": "sha512-pdUIIdj/C93ryCHew0UgBnL2DtUS3hfFa5XtERrs4x+hmpMYGhbzo6l/Ir5de41O0GaKVpK1ZbDNXSY6GkXvtg==", "dev": true, - "license": "MIT", "dependencies": { - "colord": "^2.9.1", - "cssnano-utils": "^4.0.1", + "colord": "^2.9.3", + "cssnano-utils": "^5.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-minify-params": { - "version": "6.0.2", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.2.tgz", + "integrity": "sha512-nyqVLu4MFl9df32zTsdcLqCFfE/z2+f8GE1KHPxWOAmegSo6lpV2GNy5XQvrzwbLmiU7d+fYay4cwto1oNdAaQ==", "dev": true, - "license": "MIT", "dependencies": { - "browserslist": "^4.22.2", - "cssnano-utils": "^4.0.1", + "browserslist": "^4.23.3", + "cssnano-utils": "^5.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-minify-selectors": { - "version": "6.0.2", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.4.tgz", + "integrity": "sha512-JG55VADcNb4xFCf75hXkzc1rNeURhlo7ugf6JjiiKRfMsKlDzN9CXHZDyiG6x/zGchpjQS+UAgb1d4nqXqOpmA==", "dev": true, - "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.15" + "cssesc": "^3.0.0", + "postcss-selector-parser": "^6.1.2" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, - "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -44211,12 +43274,13 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.3", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "dev": true, - "license": "MIT", "dependencies": { "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" }, "engines": { @@ -44226,12 +43290,26 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-modules-scope": { - "version": "3.0.0", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "dev": true, - "license": "ISC", "dependencies": { - "postcss-selector-parser": "^6.0.4" + "postcss-selector-parser": "^7.0.0" }, "engines": { "node": "^10 || ^12 || >= 14" @@ -44240,6 +43318,19 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-modules-values": { "version": "4.0.0", "dev": true, @@ -44255,177 +43346,190 @@ } }, "node_modules/postcss-normalize-charset": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.0.tgz", + "integrity": "sha512-ABisNUXMeZeDNzCQxPxBCkXexvBrUHV+p7/BXOY+ulxkcjUZO0cp8ekGBwvIh2LbCwnWbyMPNJVtBSdyhM2zYQ==", "dev": true, - "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-display-values": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.0.tgz", + "integrity": "sha512-lnFZzNPeDf5uGMPYgGOw7v0BfB45+irSRz9gHQStdkkhiM0gTfvWkWB5BMxpn0OqgOQuZG/mRlZyJxp0EImr2Q==", "dev": true, - "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-positions": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.0.tgz", + "integrity": "sha512-I0yt8wX529UKIGs2y/9Ybs2CelSvItfmvg/DBIjTnoUSrPxSV7Z0yZ8ShSVtKNaV/wAY+m7bgtyVQLhB00A1NQ==", "dev": true, - "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-repeat-style": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.0.tgz", + "integrity": "sha512-o3uSGYH+2q30ieM3ppu9GTjSXIzOrRdCUn8UOMGNw7Af61bmurHTWI87hRybrP6xDHvOe5WlAj3XzN6vEO8jLw==", "dev": true, - "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-string": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.0.tgz", + "integrity": "sha512-w/qzL212DFVOpMy3UGyxrND+Kb0fvCiBBujiaONIihq7VvtC7bswjWgKQU/w4VcRyDD8gpfqUiBQ4DUOwEJ6Qg==", "dev": true, - "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-timing-functions": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.0.tgz", + "integrity": "sha512-tNgw3YV0LYoRwg43N3lTe3AEWZ66W7Dh7lVEpJbHoKOuHc1sLrzMLMFjP8SNULHaykzsonUEDbKedv8C+7ej6g==", "dev": true, - "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-unicode": { - "version": "6.0.2", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.2.tgz", + "integrity": "sha512-ztisabK5C/+ZWBdYC+Y9JCkp3M9qBv/XFvDtSw0d/XwfT3UaKeW/YTm/MD/QrPNxuecia46vkfEhewjwcYFjkg==", "dev": true, - "license": "MIT", "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.3", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-url": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.0.tgz", + "integrity": "sha512-+d7+PpE+jyPX1hDQZYG+NaFD+Nd2ris6r8fPTBAjE8z/U41n/bib3vze8x7rKs5H1uEw5ppe9IojewouHk0klQ==", "dev": true, - "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-whitespace": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.0.tgz", + "integrity": "sha512-37/toN4wwZErqohedXYqWgvcHUGlT8O/m2jVkAfAe9Bd4MzRqlBmXrJRePH0e9Wgnz2X7KymTgTOaaFizQe3AQ==", "dev": true, - "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-ordered-values": { - "version": "6.0.1", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.1.tgz", + "integrity": "sha512-irWScWRL6nRzYmBOXReIKch75RRhNS86UPUAxXdmW/l0FcAsg0lvAXQCby/1lymxn/o0gVa6Rv/0f03eJOwHxw==", "dev": true, - "license": "MIT", "dependencies": { - "cssnano-utils": "^4.0.1", + "cssnano-utils": "^5.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-reduce-initial": { - "version": "6.0.2", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.2.tgz", + "integrity": "sha512-pOnu9zqQww7dEKf62Nuju6JgsW2V0KRNBHxeKohU+JkHd/GAH5uvoObqFLqkeB2n20mr6yrlWDvo5UBU5GnkfA==", "dev": true, - "license": "MIT", "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.3", "caniuse-api": "^3.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-reduce-transforms": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.0.tgz", + "integrity": "sha512-pnt1HKKZ07/idH8cpATX/ujMbtOGhUfE+m8gbqwJE05aTaNw8gbo34a2e3if0xc0dlu75sUOiqvwCGY3fzOHew==", "dev": true, - "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-selector-parser": { - "version": "6.0.15", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "devOptional": true, - "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -44435,29 +43539,31 @@ } }, "node_modules/postcss-svgo": { - "version": "6.0.2", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.0.1.tgz", + "integrity": "sha512-0WBUlSL4lhD9rA5k1e5D8EN5wCEyZD6HJk0jIvRxl+FDVOMlJ7DePHYWGGVc5QRqrJ3/06FTXM0bxjmJpmTPSA==", "dev": true, - "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" + "svgo": "^3.3.2" }, "engines": { - "node": "^14 || ^16 || >= 18" + "node": "^18.12.0 || ^20.9.0 || >= 18" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-unique-selectors": { - "version": "6.0.2", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.3.tgz", + "integrity": "sha512-J+58u5Ic5T1QjP/LDV9g3Cx4CNOgB5vz+kM6+OxHHhFACdcDeKhBXjQmB7fnIZM12YSTvsL0Opwco83DmacW2g==", "dev": true, - "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.15" + "postcss-selector-parser": "^6.1.2" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" @@ -44569,6 +43675,7 @@ }, "node_modules/pretty-error": { "version": "4.0.0", + "dev": true, "license": "MIT", "dependencies": { "lodash": "^4.17.20", @@ -45262,7 +44369,7 @@ }, "node_modules/randombytes": { "version": "2.1.0", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" @@ -45616,57 +44723,6 @@ "react-dom": ">=16.9.0" } }, - "node_modules/rc-picker": { - "version": "4.5.0", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.10.1", - "@rc-component/trigger": "^2.0.0", - "classnames": "^2.2.1", - "rc-overflow": "^1.3.2", - "rc-resize-observer": "^1.4.0", - "rc-util": "^5.38.1" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "date-fns": ">= 2.x", - "dayjs": ">= 1.x", - "luxon": ">= 3.x", - "moment": ">= 2.x", - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - }, - "peerDependenciesMeta": { - "date-fns": { - "optional": true - }, - "dayjs": { - "optional": true - }, - "luxon": { - "optional": true - }, - "moment": { - "optional": true - } - } - }, - "node_modules/rc-picker/node_modules/rc-resize-observer": { - "version": "1.4.0", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.20.7", - "classnames": "^2.2.1", - "rc-util": "^5.38.0", - "resize-observer-polyfill": "^1.5.1" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, "node_modules/rc-progress": { "version": "3.1.1", "license": "MIT", @@ -46428,27 +45484,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/react-move": { - "version": "2.9.1", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.2.0", - "d3-interpolate": "^1.3.2", - "d3-timer": "^1.0.9", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" - }, - "peerDependencies": { - "react": "^15.4.0 || ^16.0.0" - } - }, - "node_modules/react-move/node_modules/d3-interpolate": { - "version": "1.4.0", - "license": "BSD-3-Clause", - "dependencies": { - "d3-color": "1" - } - }, "node_modules/react-redux": { "version": "7.2.9", "license": "MIT", @@ -47762,6 +46797,7 @@ }, "node_modules/relateurl": { "version": "0.2.7", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.10" @@ -48541,6 +47577,7 @@ }, "node_modules/renderkid": { "version": "3.0.0", + "dev": true, "license": "MIT", "dependencies": { "css-select": "^4.1.3", @@ -48552,6 +47589,7 @@ }, "node_modules/renderkid/node_modules/css-select": { "version": "4.3.0", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", @@ -48566,6 +47604,7 @@ }, "node_modules/renderkid/node_modules/dom-serializer": { "version": "1.4.1", + "dev": true, "license": "MIT", "dependencies": { "domelementtype": "^2.0.1", @@ -48578,6 +47617,7 @@ }, "node_modules/renderkid/node_modules/domhandler": { "version": "4.3.1", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.2.0" @@ -48591,6 +47631,7 @@ }, "node_modules/renderkid/node_modules/domutils": { "version": "2.8.0", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^1.0.1", @@ -48603,6 +47644,7 @@ }, "node_modules/renderkid/node_modules/entities": { "version": "2.2.0", + "dev": true, "license": "BSD-2-Clause", "funding": { "url": "https://github.com/fb55/entities?sponsor=1" @@ -48610,6 +47652,7 @@ }, "node_modules/renderkid/node_modules/htmlparser2": { "version": "6.1.0", + "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -49293,7 +48336,7 @@ }, "node_modules/schema-utils": { "version": "3.3.0", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", @@ -49420,7 +48463,7 @@ }, "node_modules/serialize-javascript": { "version": "6.0.2", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" @@ -50195,9 +49238,10 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -50216,6 +49260,7 @@ }, "node_modules/source-map-support": { "version": "0.5.21", + "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -50224,6 +49269,7 @@ }, "node_modules/source-map-support/node_modules/source-map": { "version": "0.6.1", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -50969,28 +50015,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stringify-entities": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/stringify-entities/node_modules/character-entities-legacy": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "license": "MIT", @@ -51168,15 +50192,16 @@ } }, "node_modules/stylehacks": { - "version": "6.0.2", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.4.tgz", + "integrity": "sha512-i4zfNrGMt9SB4xRK9L83rlsFCgdGANfeDAYacO1pkqcE7cRHPdWHwnKZVz7WY17Veq/FvyYsRAU++Ga+qDFIww==", "dev": true, - "license": "MIT", "dependencies": { - "browserslist": "^4.22.2", - "postcss-selector-parser": "^6.0.15" + "browserslist": "^4.23.3", + "postcss-selector-parser": "^6.1.2" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" @@ -51229,9 +50254,10 @@ } }, "node_modules/svgo": { - "version": "3.2.0", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", "dev": true, - "license": "MIT", "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", @@ -51578,6 +50604,7 @@ }, "node_modules/terser": { "version": "5.27.0", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -51594,7 +50621,7 @@ }, "node_modules/terser-webpack-plugin": { "version": "5.3.10", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.20", @@ -51627,7 +50654,7 @@ }, "node_modules/terser-webpack-plugin/node_modules/has-flag": { "version": "4.0.0", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -51635,7 +50662,7 @@ }, "node_modules/terser-webpack-plugin/node_modules/jest-worker": { "version": "27.5.1", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -51648,7 +50675,7 @@ }, "node_modules/terser-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -52303,7 +51330,7 @@ }, "node_modules/tslib": { "version": "1.11.1", - "devOptional": true, + "dev": true, "license": "Apache-2.0" }, "node_modules/tsutils": { @@ -52762,31 +51789,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-position-from-estree": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-remove-position": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-visit": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-stringify-position": { "version": "3.0.2", "license": "MIT", @@ -52926,6 +51928,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "devOptional": true, "funding": [ { "type": "opencollective", @@ -53121,6 +52124,7 @@ }, "node_modules/utila": { "version": "0.4.0", + "dev": true, "license": "MIT" }, "node_modules/utils-merge": { @@ -53487,7 +52491,7 @@ }, "node_modules/watchpack": { "version": "2.4.1", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", @@ -53573,7 +52577,7 @@ "version": "5.97.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", - "devOptional": true, + "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -54160,7 +53164,7 @@ }, "node_modules/webpack-sources": { "version": "3.2.3", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" @@ -54226,7 +53230,7 @@ }, "node_modules/webpack/node_modules/enhanced-resolve": { "version": "5.17.1", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -54238,7 +53242,7 @@ }, "node_modules/webpack/node_modules/tapable": { "version": "2.2.1", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -54896,66 +53900,6 @@ } } }, - "node_modules/xdm": { - "version": "3.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^4.0.0", - "@types/estree-jsx": "^0.0.1", - "@types/mdx": "^2.0.0", - "astring": "^1.6.0", - "estree-util-build-jsx": "^2.0.0", - "estree-util-is-identifier-name": "^2.0.0", - "estree-walker": "^3.0.0", - "hast-util-to-estree": "^2.0.0", - "markdown-extensions": "^1.0.0", - "mdast-util-mdx": "^2.0.0", - "micromark-extension-mdxjs": "^1.0.0", - "node-fetch": "^3.2.0", - "periscopic": "^3.0.0", - "remark-parse": "^10.0.0", - "remark-rehype": "^10.0.0", - "source-map": "^0.7.0", - "unified": "^10.0.0", - "unist-util-position-from-estree": "^1.0.0", - "unist-util-stringify-position": "^3.0.0", - "unist-util-visit": "^4.0.0", - "vfile": "^5.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - }, - "optionalDependencies": { - "deasync": "^0.1.0" - } - }, - "node_modules/xdm/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/xdm/node_modules/node-fetch": { - "version": "3.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, "node_modules/xml-name-validator": { "version": "3.0.0", "dev": true, @@ -55009,6 +53953,7 @@ }, "node_modules/yallist": { "version": "3.1.1", + "devOptional": true, "license": "ISC" }, "node_modules/yaml": { @@ -57165,17 +56110,12 @@ "@superset-ui/legacy-plugin-chart-calendar": "*", "@superset-ui/legacy-plugin-chart-chord": "*", "@superset-ui/legacy-plugin-chart-country-map": "*", - "@superset-ui/legacy-plugin-chart-event-flow": "*", - "@superset-ui/legacy-plugin-chart-heatmap": "*", - "@superset-ui/legacy-plugin-chart-histogram": "*", "@superset-ui/legacy-plugin-chart-horizon": "*", "@superset-ui/legacy-plugin-chart-map-box": "*", "@superset-ui/legacy-plugin-chart-paired-t-test": "*", "@superset-ui/legacy-plugin-chart-parallel-coordinates": "*", "@superset-ui/legacy-plugin-chart-partition": "*", "@superset-ui/legacy-plugin-chart-rose": "*", - "@superset-ui/legacy-plugin-chart-sankey": "*", - "@superset-ui/legacy-plugin-chart-sankey-loop": "*", "@superset-ui/legacy-plugin-chart-time-table": "*", "@superset-ui/legacy-plugin-chart-world-map": "*", "@superset-ui/legacy-preset-chart-deckgl": "*", @@ -58058,6 +56998,41 @@ "url": "https://opencollective.com/core-js" } }, + "packages/superset-ui-demo/node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, "packages/superset-ui-demo/node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -58688,6 +57663,7 @@ "plugins/legacy-plugin-chart-event-flow": { "name": "@superset-ui/legacy-plugin-chart-event-flow", "version": "0.20.3", + "extraneous": true, "license": "Apache-2.0", "dependencies": { "@data-ui/event-flow": "^0.0.84", @@ -58702,6 +57678,7 @@ "plugins/legacy-plugin-chart-heatmap": { "name": "@superset-ui/legacy-plugin-chart-heatmap", "version": "0.20.3", + "extraneous": true, "license": "Apache-2.0", "dependencies": { "d3": "^3.5.17", @@ -58719,6 +57696,7 @@ "plugins/legacy-plugin-chart-histogram": { "name": "@superset-ui/legacy-plugin-chart-histogram", "version": "0.20.3", + "extraneous": true, "license": "Apache-2.0", "dependencies": { "@data-ui/histogram": "^0.0.84", @@ -58734,34 +57712,6 @@ "react": "^15 || ^16" } }, - "plugins/legacy-plugin-chart-histogram/node_modules/@vx/group": { - "version": "0.0.199", - "license": "MIT", - "dependencies": { - "@types/classnames": "^2.2.9", - "@types/react": "*", - "classnames": "^2.2.5", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" - } - }, - "plugins/legacy-plugin-chart-histogram/node_modules/@vx/legend": { - "version": "0.0.199", - "license": "MIT", - "dependencies": { - "@types/classnames": "^2.2.9", - "@types/react": "*", - "@vx/group": "0.0.199", - "@vx/scale": "0.0.199", - "classnames": "^2.2.5", - "prop-types": "^15.5.10" - }, - "peerDependencies": { - "react": "^16.3.0-0" - } - }, "plugins/legacy-plugin-chart-horizon": { "name": "@superset-ui/legacy-plugin-chart-horizon", "version": "0.20.3", @@ -58926,6 +57876,7 @@ "plugins/legacy-plugin-chart-sankey": { "name": "@superset-ui/legacy-plugin-chart-sankey", "version": "0.20.3", + "extraneous": true, "license": "Apache-2.0", "dependencies": { "d3": "^3.5.17", @@ -58941,6 +57892,7 @@ "plugins/legacy-plugin-chart-sankey-loop": { "name": "@superset-ui/legacy-plugin-chart-sankey-loop", "version": "0.20.3", + "extraneous": true, "license": "Apache-2.0", "dependencies": { "d3-sankey-diagram": "^0.7.3", @@ -59542,6 +58494,7 @@ }, "@ampproject/remapping": { "version": "2.2.0", + "devOptional": true, "requires": { "@jridgewell/gen-mapping": "^0.1.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -59549,6 +58502,7 @@ "dependencies": { "@jridgewell/gen-mapping": { "version": "0.1.1", + "devOptional": true, "requires": { "@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -59575,11 +58529,13 @@ } }, "@ant-design/icons": { - "version": "5.3.7", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.5.2.tgz", + "integrity": "sha512-xc53rjVBl9v2BqFxUjZGti/RfdDeA8/6KYglmInM2PNqSXc/WfuGDTifJI/ZsokJK0aeKvOIbXc9y2g8ILAhEA==", "requires": { "@ant-design/colors": "^7.0.0", "@ant-design/icons-svg": "^4.4.0", - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.24.8", "classnames": "^2.2.6", "rc-util": "^5.31.1" } @@ -60225,10 +59181,12 @@ } }, "@babel/compat-data": { - "version": "7.24.7" + "version": "7.24.7", + "devOptional": true }, "@babel/core": { "version": "7.24.7", + "devOptional": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", @@ -60248,22 +59206,27 @@ }, "dependencies": { "convert-source-map": { - "version": "2.0.0" + "version": "2.0.0", + "devOptional": true }, "debug": { "version": "4.3.1", + "devOptional": true, "requires": { "ms": "2.1.2" } }, "json5": { - "version": "2.2.3" + "version": "2.2.3", + "devOptional": true }, "ms": { - "version": "2.1.2" + "version": "2.1.2", + "devOptional": true }, "semver": { - "version": "6.3.1" + "version": "6.3.1", + "devOptional": true } } }, @@ -60314,6 +59277,7 @@ }, "@babel/helper-compilation-targets": { "version": "7.24.7", + "devOptional": true, "requires": { "@babel/compat-data": "^7.24.7", "@babel/helper-validator-option": "^7.24.7", @@ -60323,7 +59287,8 @@ }, "dependencies": { "semver": { - "version": "6.3.1" + "version": "6.3.1", + "devOptional": true } } }, @@ -60423,6 +59388,7 @@ }, "@babel/helper-module-transforms": { "version": "7.24.7", + "devOptional": true, "requires": { "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-module-imports": "^7.24.7", @@ -60439,7 +59405,8 @@ } }, "@babel/helper-plugin-utils": { - "version": "7.24.7" + "version": "7.24.7", + "dev": true }, "@babel/helper-remap-async-to-generator": { "version": "7.24.7", @@ -60461,6 +59428,7 @@ }, "@babel/helper-simple-access": { "version": "7.24.7", + "devOptional": true, "requires": { "@babel/traverse": "^7.24.7", "@babel/types": "^7.24.7" @@ -60489,7 +59457,8 @@ "version": "7.24.7" }, "@babel/helper-validator-option": { - "version": "7.24.7" + "version": "7.24.7", + "devOptional": true }, "@babel/helper-wrap-function": { "version": "7.24.7", @@ -60503,6 +59472,7 @@ }, "@babel/helpers": { "version": "7.24.7", + "devOptional": true, "requires": { "@babel/template": "^7.24.7", "@babel/types": "^7.24.7" @@ -60771,6 +59741,7 @@ }, "@babel/plugin-syntax-typescript": { "version": "7.23.3", + "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } @@ -61468,7 +60439,9 @@ "dev": true }, "@babel/runtime": { - "version": "7.24.7", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -61783,161 +60756,6 @@ } } }, - "@data-ui/histogram": { - "version": "0.0.84", - "requires": { - "@data-ui/shared": "^0.0.84", - "@data-ui/theme": "^0.0.84", - "@vx/axis": "^0.0.179", - "@vx/curve": "^0.0.165", - "@vx/event": "^0.0.179", - "@vx/glyph": "^0.0.179", - "@vx/gradient": "^0.0.165", - "@vx/group": "^0.0.170", - "@vx/pattern": "^0.0.179", - "@vx/responsive": "^0.0.192", - "@vx/scale": "^0.0.179", - "@vx/shape": "^0.0.179", - "@vx/tooltip": "0.0.179", - "d3-array": "^1.2.0", - "d3-scale": "^1.0.6", - "prop-types": "^15.5.10", - "react-move": "^2.1.0" - }, - "dependencies": { - "@vx/axis": { - "version": "0.0.179", - "requires": { - "@vx/group": "0.0.170", - "@vx/point": "0.0.165", - "@vx/shape": "0.0.179", - "@vx/text": "0.0.179", - "classnames": "^2.2.5", - "prop-types": "^15.6.0" - } - }, - "@vx/bounds": { - "version": "0.0.165", - "requires": { - "prop-types": "^15.5.10" - } - }, - "@vx/event": { - "version": "0.0.179", - "requires": { - "@vx/point": "0.0.165" - } - }, - "@vx/glyph": { - "version": "0.0.179", - "requires": { - "@vx/group": "0.0.170", - "classnames": "^2.2.5", - "d3-shape": "^1.2.0", - "prop-types": "^15.6.2" - } - }, - "@vx/gradient": { - "version": "0.0.165", - "requires": { - "classnames": "^2.2.5", - "prop-types": "^15.5.7" - } - }, - "@vx/group": { - "version": "0.0.170", - "requires": { - "classnames": "^2.2.5" - } - }, - "@vx/pattern": { - "version": "0.0.179", - "requires": { - "classnames": "^2.2.5", - "prop-types": "^15.5.10" - } - }, - "@vx/point": { - "version": "0.0.165" - }, - "@vx/responsive": { - "version": "0.0.192", - "requires": { - "lodash": "^4.17.10", - "prop-types": "^15.6.1", - "resize-observer-polyfill": "1.5.0" - } - }, - "@vx/scale": { - "version": "0.0.179", - "requires": { - "d3-scale": "^2.0.0" - }, - "dependencies": { - "d3-scale": { - "version": "2.2.2", - "requires": { - "d3-array": "^1.2.0", - "d3-collection": "1", - "d3-format": "1", - "d3-interpolate": "1", - "d3-time": "1", - "d3-time-format": "2" - } - } - } - }, - "@vx/shape": { - "version": "0.0.179", - "requires": { - "@vx/curve": "0.0.165", - "@vx/group": "0.0.170", - "@vx/point": "0.0.165", - "classnames": "^2.2.5", - "d3-path": "^1.0.5", - "d3-shape": "^1.2.0", - "prop-types": "^15.5.10" - } - }, - "@vx/tooltip": { - "version": "0.0.179", - "requires": { - "@vx/bounds": "0.0.165", - "classnames": "^2.2.5", - "prop-types": "^15.5.10" - } - }, - "d3-interpolate": { - "version": "1.4.0", - "requires": { - "d3-color": "^3.1.0" - } - }, - "d3-scale": { - "version": "1.0.7", - "requires": { - "d3-array": "^1.2.0", - "d3-collection": "1", - "d3-color": "^3.1.0", - "d3-format": "1", - "d3-interpolate": "1", - "d3-time": "1", - "d3-time-format": "2" - } - }, - "d3-time-format": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", - "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", - "requires": { - "d3-time": "1" - } - }, - "resize-observer-polyfill": { - "version": "1.5.0" - } - } - }, "@data-ui/radial-chart": { "version": "0.0.84", "requires": { @@ -63646,6 +62464,7 @@ }, "@jridgewell/source-map": { "version": "0.3.5", + "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -65780,20 +64599,6 @@ } } }, - "@rollup/pluginutils": { - "version": "4.2.1", - "dev": true, - "requires": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" - }, - "dependencies": { - "estree-walker": { - "version": "2.0.2", - "dev": true - } - } - }, "@scarf/scarf": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", @@ -66200,6 +65005,22 @@ "undici-types": "~5.26.4" } }, + "css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + } + }, "deepmerge": { "version": "4.3.1", "dev": true @@ -68387,6 +67208,22 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==" }, + "css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + } + }, "debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -69622,55 +68459,6 @@ } } }, - "@superset-ui/legacy-plugin-chart-event-flow": { - "version": "file:plugins/legacy-plugin-chart-event-flow", - "requires": { - "@data-ui/event-flow": "^0.0.84", - "prop-types": "^15.8.1" - } - }, - "@superset-ui/legacy-plugin-chart-heatmap": { - "version": "file:plugins/legacy-plugin-chart-heatmap", - "requires": { - "d3": "^3.5.17", - "d3-svg-legend": "^1.x", - "d3-tip": "^0.9.1", - "prop-types": "^15.8.1" - } - }, - "@superset-ui/legacy-plugin-chart-histogram": { - "version": "file:plugins/legacy-plugin-chart-histogram", - "requires": { - "@data-ui/histogram": "^0.0.84", - "@data-ui/theme": "^0.0.84", - "@vx/legend": "^0.0.199", - "@vx/responsive": "^0.0.199", - "@vx/scale": "^0.0.199", - "prop-types": "^15.8.1" - }, - "dependencies": { - "@vx/group": { - "version": "0.0.199", - "requires": { - "@types/classnames": "^2.2.9", - "@types/react": "*", - "classnames": "^2.2.5", - "prop-types": "^15.6.2" - } - }, - "@vx/legend": { - "version": "0.0.199", - "requires": { - "@types/classnames": "^2.2.9", - "@types/react": "*", - "@vx/group": "0.0.199", - "@vx/scale": "0.0.199", - "classnames": "^2.2.5", - "prop-types": "^15.5.10" - } - } - } - }, "@superset-ui/legacy-plugin-chart-horizon": { "version": "file:plugins/legacy-plugin-chart-horizon", "requires": { @@ -69783,22 +68571,6 @@ "prop-types": "^15.8.1" } }, - "@superset-ui/legacy-plugin-chart-sankey": { - "version": "file:plugins/legacy-plugin-chart-sankey", - "requires": { - "d3": "^3.5.17", - "d3-sankey": "^0.4.2", - "prop-types": "^15.8.1" - } - }, - "@superset-ui/legacy-plugin-chart-sankey-loop": { - "version": "file:plugins/legacy-plugin-chart-sankey-loop", - "requires": { - "d3-sankey-diagram": "^0.7.3", - "d3-selection": "^3.0.0", - "prop-types": "^15.8.1" - } - }, "@superset-ui/legacy-plugin-chart-time-table": { "version": "0.18.25", "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-time-table/-/legacy-plugin-chart-time-table-0.18.25.tgz", @@ -70653,13 +69425,6 @@ } } }, - "@types/acorn": { - "version": "4.0.6", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, "@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -70897,7 +69662,7 @@ "version": "8.56.11", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.11.tgz", "integrity": "sha512-sVBpJMf7UPo/wGecYOpk2aQya2VUGeHhe38WG7/mN5FufNSubf5VT9Uh9Uyp8/eLJpu1/tuhJ/qTo4mhSB4V4Q==", - "devOptional": true, + "dev": true, "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -70907,7 +69672,7 @@ "version": "3.7.7", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "devOptional": true, + "dev": true, "requires": { "@types/eslint": "*", "@types/estree": "*" @@ -70917,14 +69682,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "devOptional": true - }, - "@types/estree-jsx": { - "version": "0.0.1", - "dev": true, - "requires": { - "@types/estree": "*" - } + "dev": true }, "@types/expect": { "version": "1.20.4" @@ -71017,7 +69775,8 @@ } }, "@types/html-minifier-terser": { - "version": "6.1.0" + "version": "6.1.0", + "dev": true }, "@types/http-cache-semantics": { "version": "4.0.4" @@ -72244,66 +71003,6 @@ "resize-observer-polyfill": "1.5.1" } }, - "@vx/scale": { - "version": "0.0.199", - "requires": { - "@types/d3-interpolate": "^1.3.1", - "@types/d3-scale": "^2.1.1", - "@types/d3-time": "^1.0.10", - "d3-interpolate": "^1.4.0", - "d3-scale": "^3.0.1", - "d3-time": "^1.1.0" - }, - "dependencies": { - "@types/d3-interpolate": { - "version": "1.4.5", - "requires": { - "@types/d3-color": "^1" - } - }, - "d3-array": { - "version": "2.12.1", - "requires": { - "internmap": "^1.0.0" - } - }, - "d3-interpolate": { - "version": "1.4.0", - "requires": { - "d3-color": "^3.1.0" - } - }, - "d3-scale": { - "version": "3.3.0", - "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-time": { - "version": "2.1.1", - "requires": { - "d3-array": "2" - } - } - } - }, - "d3-time": { - "version": "1.1.0" - }, - "d3-time-format": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", - "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", - "requires": { - "d3-time": "1 - 2" - } - } - } - }, "@vx/shape": { "version": "0.0.140", "requires": { @@ -72348,6 +71047,7 @@ }, "@vx/text": { "version": "0.0.179", + "peer": true, "requires": { "babel-plugin-lodash": "^3.3.2", "classnames": "^2.2.5", @@ -72524,7 +71224,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -72534,25 +71234,25 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "devOptional": true + "dev": true }, "@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "devOptional": true + "dev": true }, "@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "devOptional": true + "dev": true }, "@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -72563,13 +71263,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "devOptional": true + "dev": true }, "@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -72581,7 +71281,7 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "devOptional": true, + "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } @@ -72590,7 +71290,7 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "devOptional": true, + "dev": true, "requires": { "@xtuc/long": "4.2.2" } @@ -72599,13 +71299,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "devOptional": true + "dev": true }, "@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -72621,7 +71321,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -72634,7 +71334,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -72646,7 +71346,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -72660,7 +71360,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -72693,13 +71393,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "devOptional": true + "dev": true }, "@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "devOptional": true + "dev": true }, "@yarnpkg/esbuild-plugin-pnp": { "version": "3.0.0-rc.15", @@ -72956,7 +71656,7 @@ }, "ajv": { "version": "6.12.6", - "devOptional": true, + "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -72986,7 +71686,7 @@ }, "ajv-keywords": { "version": "3.5.2", - "devOptional": true, + "dev": true, "requires": {} }, "ansi-align": { @@ -73090,12 +71790,15 @@ } }, "@ant-design/icons": { - "version": "4.8.0", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.8.3.tgz", + "integrity": "sha512-HGlIQZzrEbAhpJR6+IGdzfbPym94Owr6JZkJ2QCCnOkPVIWMO2xgIVcOKnl8YcpijIo39V7l2qQL5fmtw56cMw==", "requires": { "@ant-design/colors": "^6.0.0", - "@ant-design/icons-svg": "^4.2.1", + "@ant-design/icons-svg": "^4.3.0", "@babel/runtime": "^7.11.2", "classnames": "^2.2.6", + "lodash": "^4.17.15", "rc-util": "^5.9.4" }, "dependencies": { @@ -73383,6 +72086,19 @@ "rc-util": "^5.38.0" } }, + "rc-picker": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.5.0.tgz", + "integrity": "sha512-suqz9bzuhBQlf7u+bZd1bJLPzhXpk12w6AjQ9BTPTiFwexVZgUKViG1KNLyfFvW6tCUZZK0HmCCX7JAyM+JnCg==", + "requires": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.38.1" + } + }, "rc-progress": { "version": "4.0.0", "requires": { @@ -73734,10 +72450,6 @@ "dev": true, "peer": true }, - "astring": { - "version": "1.8.6", - "dev": true - }, "async": { "version": "3.2.4" }, @@ -74102,6 +72814,7 @@ }, "babel-plugin-typescript-to-proptypes": { "version": "2.1.0", + "dev": true, "requires": { "@babel/helper-module-imports": "^7.15.4", "@babel/plugin-syntax-typescript": "^7.14.5", @@ -74508,7 +73221,8 @@ } }, "boolbase": { - "version": "1.0.0" + "version": "1.0.0", + "dev": true }, "bootstrap": { "version": "3.4.1" @@ -74582,6 +73296,7 @@ "version": "4.24.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", + "devOptional": true, "requires": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -74867,13 +73582,15 @@ }, "camel-case": { "version": "4.1.2", + "dev": true, "requires": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" }, "dependencies": { "tslib": { - "version": "2.6.2" + "version": "2.6.2", + "dev": true } } }, @@ -74894,6 +73611,8 @@ }, "caniuse-api": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -74905,7 +73624,8 @@ "caniuse-lite": { "version": "1.0.30001689", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz", - "integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==" + "integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==", + "devOptional": true }, "canvg": { "version": "3.0.10", @@ -74986,10 +73706,6 @@ "character-entities": { "version": "1.2.2" }, - "character-entities-html4": { - "version": "2.1.0", - "dev": true - }, "character-entities-legacy": { "version": "1.1.2" }, @@ -75116,7 +73832,7 @@ }, "chrome-trace-event": { "version": "1.0.2", - "devOptional": true, + "dev": true, "requires": { "tslib": "^1.9.0" } @@ -75189,12 +73905,14 @@ }, "clean-css": { "version": "5.3.3", + "dev": true, "requires": { "source-map": "~0.6.0" }, "dependencies": { "source-map": { - "version": "0.6.1" + "version": "0.6.1", + "dev": true } } }, @@ -75356,6 +74074,8 @@ }, "colord": { "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", "dev": true }, "colorette": { @@ -75980,7 +74700,9 @@ } }, "css-declaration-sorter": { - "version": "7.1.1", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", + "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", "dev": true, "requires": {} }, @@ -76006,17 +74728,19 @@ } }, "css-loader": { - "version": "6.8.1", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", "dev": true, "requires": { "icss-utils": "^5.1.0", - "postcss": "^8.4.21", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.3", - "postcss-modules-scope": "^3.0.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "dependencies": { "lru-cache": { @@ -76040,15 +74764,17 @@ } }, "css-minimizer-webpack-plugin": { - "version": "5.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-7.0.0.tgz", + "integrity": "sha512-niy66jxsQHqO+EYbhPuIhqRQ1mNcNVUHrMnkzzir9kFOERJUaQDDRhh7dKDz33kBpkWMF9M8Vx0QlDbc5AHOsw==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.18", - "cssnano": "^6.0.1", - "jest-worker": "^29.4.3", - "postcss": "^8.4.24", - "schema-utils": "^4.0.1", - "serialize-javascript": "^6.0.1" + "@jridgewell/trace-mapping": "^0.3.25", + "cssnano": "^7.0.1", + "jest-worker": "^29.7.0", + "postcss": "^8.4.38", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" }, "dependencies": { "ajv": { @@ -76096,7 +74822,8 @@ } }, "css-what": { - "version": "6.1.0" + "version": "6.1.0", + "dev": true }, "css.escape": { "version": "1.5.1" @@ -76112,50 +74839,57 @@ "version": "0.0.10" }, "cssnano": { - "version": "6.0.3", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.6.tgz", + "integrity": "sha512-54woqx8SCbp8HwvNZYn68ZFAepuouZW4lTwiMVnBErM3VkO7/Sd4oTOt3Zz3bPx3kxQ36aISppyXj2Md4lg8bw==", "dev": true, "requires": { - "cssnano-preset-default": "^6.0.3", - "lilconfig": "^3.0.0" + "cssnano-preset-default": "^7.0.6", + "lilconfig": "^3.1.2" } }, "cssnano-preset-default": { - "version": "6.0.3", - "dev": true, - "requires": { - "css-declaration-sorter": "^7.1.1", - "cssnano-utils": "^4.0.1", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.0.2", - "postcss-convert-values": "^6.0.2", - "postcss-discard-comments": "^6.0.1", - "postcss-discard-duplicates": "^6.0.1", - "postcss-discard-empty": "^6.0.1", - "postcss-discard-overridden": "^6.0.1", - "postcss-merge-longhand": "^6.0.2", - "postcss-merge-rules": "^6.0.3", - "postcss-minify-font-values": "^6.0.1", - "postcss-minify-gradients": "^6.0.1", - "postcss-minify-params": "^6.0.2", - "postcss-minify-selectors": "^6.0.2", - "postcss-normalize-charset": "^6.0.1", - "postcss-normalize-display-values": "^6.0.1", - "postcss-normalize-positions": "^6.0.1", - "postcss-normalize-repeat-style": "^6.0.1", - "postcss-normalize-string": "^6.0.1", - "postcss-normalize-timing-functions": "^6.0.1", - "postcss-normalize-unicode": "^6.0.2", - "postcss-normalize-url": "^6.0.1", - "postcss-normalize-whitespace": "^6.0.1", - "postcss-ordered-values": "^6.0.1", - "postcss-reduce-initial": "^6.0.2", - "postcss-reduce-transforms": "^6.0.1", - "postcss-svgo": "^6.0.2", - "postcss-unique-selectors": "^6.0.2" + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.6.tgz", + "integrity": "sha512-ZzrgYupYxEvdGGuqL+JKOY70s7+saoNlHSCK/OGn1vB2pQK8KSET8jvenzItcY+kA7NoWvfbb/YhlzuzNKjOhQ==", + "dev": true, + "requires": { + "browserslist": "^4.23.3", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^5.0.0", + "postcss-calc": "^10.0.2", + "postcss-colormin": "^7.0.2", + "postcss-convert-values": "^7.0.4", + "postcss-discard-comments": "^7.0.3", + "postcss-discard-duplicates": "^7.0.1", + "postcss-discard-empty": "^7.0.0", + "postcss-discard-overridden": "^7.0.0", + "postcss-merge-longhand": "^7.0.4", + "postcss-merge-rules": "^7.0.4", + "postcss-minify-font-values": "^7.0.0", + "postcss-minify-gradients": "^7.0.0", + "postcss-minify-params": "^7.0.2", + "postcss-minify-selectors": "^7.0.4", + "postcss-normalize-charset": "^7.0.0", + "postcss-normalize-display-values": "^7.0.0", + "postcss-normalize-positions": "^7.0.0", + "postcss-normalize-repeat-style": "^7.0.0", + "postcss-normalize-string": "^7.0.0", + "postcss-normalize-timing-functions": "^7.0.0", + "postcss-normalize-unicode": "^7.0.2", + "postcss-normalize-url": "^7.0.0", + "postcss-normalize-whitespace": "^7.0.0", + "postcss-ordered-values": "^7.0.1", + "postcss-reduce-initial": "^7.0.2", + "postcss-reduce-transforms": "^7.0.0", + "postcss-svgo": "^7.0.1", + "postcss-unique-selectors": "^7.0.3" } }, "cssnano-utils": { - "version": "4.0.1", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.0.tgz", + "integrity": "sha512-Uij0Xdxc24L6SirFr25MlwC2rCFX6scyUmuKpzI+JQ7cyqDEwD42fJ0xfB3yLfOnRDU5LKGgjQ9FA6LYh76GWQ==", "dev": true, "requires": {} }, @@ -76473,46 +75207,6 @@ "d3-queue": { "version": "2.0.3" }, - "d3-sankey": { - "version": "0.4.2", - "requires": { - "d3-array": "1", - "d3-collection": "1", - "d3-interpolate": "1" - }, - "dependencies": { - "d3-interpolate": { - "version": "1.4.0", - "requires": { - "d3-color": "^3.1.0" - } - } - } - }, - "d3-sankey-diagram": { - "version": "0.7.3", - "requires": { - "d3-array": "^1.0.2", - "d3-collection": "^1.0.2", - "d3-dispatch": "^1.0.3", - "d3-format": "^1.1.1", - "d3-interpolate": "^1.1.3", - "d3-selection": "^1.0.3", - "d3-transition": "^1.0.4", - "graphlib": "~2.1.0" - }, - "dependencies": { - "d3-interpolate": { - "version": "1.4.0", - "requires": { - "d3-color": "^3.1.0" - } - }, - "d3-selection": { - "version": "1.4.2" - } - } - }, "d3-scale": { "version": "2.2.2", "requires": { @@ -76564,10 +75258,6 @@ "d3-path": "1" } }, - "d3-svg-legend": { - "version": "1.13.0", - "requires": {} - }, "d3-time": { "version": "1.0.10" }, @@ -76751,22 +75441,6 @@ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" }, - "deasync": { - "version": "0.1.29", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "node-addon-api": "^1.7.1" - }, - "dependencies": { - "node-addon-api": { - "version": "1.7.2", - "dev": true, - "optional": true - } - } - }, "debounce": { "version": "1.2.1" }, @@ -77346,6 +76020,7 @@ }, "dom-converter": { "version": "0.2.0", + "dev": true, "requires": { "utila": "~0.4" } @@ -77385,7 +76060,8 @@ "version": "0.1.1" }, "domelementtype": { - "version": "2.3.0" + "version": "2.3.0", + "dev": true }, "domhandler": { "version": "5.0.3", @@ -77411,13 +76087,15 @@ }, "dot-case": { "version": "3.0.4", + "dev": true, "requires": { "no-case": "^3.0.4", "tslib": "^2.0.3" }, "dependencies": { "tslib": { - "version": "2.1.0" + "version": "2.1.0", + "dev": true } } }, @@ -77506,7 +76184,8 @@ "electron-to-chromium": { "version": "1.5.77", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.77.tgz", - "integrity": "sha512-AnJSrt5JpRVgY6dgd5yccguLc5A7oMSF0Kt3fcW+Hp5WTuFbl5upeSFZbMZYy2o7jhmIhU8Ekrd82GhyXUqUUg==" + "integrity": "sha512-AnJSrt5JpRVgY6dgd5yccguLc5A7oMSF0Kt3fcW+Hp5WTuFbl5upeSFZbMZYy2o7jhmIhU8Ekrd82GhyXUqUUg==", + "devOptional": true }, "email-addresses": { "version": "5.0.0" @@ -77884,7 +76563,7 @@ }, "es-module-lexer": { "version": "1.5.4", - "devOptional": true + "dev": true }, "es-object-atoms": { "version": "1.0.0", @@ -78795,7 +77474,7 @@ }, "eslint-scope": { "version": "5.1.1", - "devOptional": true, + "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -78837,73 +77516,20 @@ }, "esrecurse": { "version": "4.3.0", - "devOptional": true, + "dev": true, "requires": { "estraverse": "^5.2.0" }, "dependencies": { "estraverse": { "version": "5.2.0", - "devOptional": true + "dev": true } } }, "estraverse": { "version": "4.2.0" }, - "estree-util-attach-comments": { - "version": "2.1.1", - "dev": true, - "requires": { - "@types/estree": "^1.0.0" - } - }, - "estree-util-build-jsx": { - "version": "2.2.2", - "dev": true, - "requires": { - "@types/estree-jsx": "^1.0.0", - "estree-util-is-identifier-name": "^2.0.0", - "estree-walker": "^3.0.0" - }, - "dependencies": { - "@types/estree-jsx": { - "version": "1.0.4", - "dev": true, - "requires": { - "@types/estree": "*" - } - } - } - }, - "estree-util-is-identifier-name": { - "version": "2.1.0", - "dev": true - }, - "estree-util-visit": { - "version": "1.2.1", - "dev": true, - "requires": { - "@types/estree-jsx": "^1.0.0", - "@types/unist": "^2.0.0" - }, - "dependencies": { - "@types/estree-jsx": { - "version": "1.0.4", - "dev": true, - "requires": { - "@types/estree": "*" - } - } - } - }, - "estree-walker": { - "version": "3.0.3", - "dev": true, - "requires": { - "@types/estree": "^1.0.0" - } - }, "esutils": { "version": "2.0.2" }, @@ -79315,7 +77941,7 @@ }, "fast-json-stable-stringify": { "version": "2.1.0", - "devOptional": true + "dev": true }, "fast-json-stringify": { "version": "5.16.1", @@ -80115,7 +78741,8 @@ } }, "gensync": { - "version": "1.0.0-beta.2" + "version": "1.0.0-beta.2", + "devOptional": true }, "geojson": { "version": "0.5.0", @@ -80753,7 +79380,7 @@ }, "glob-to-regexp": { "version": "0.4.1", - "devOptional": true + "dev": true }, "global": { "version": "4.4.0", @@ -80889,12 +79516,6 @@ "version": "1.4.0", "dev": true }, - "graphlib": { - "version": "2.1.8", - "requires": { - "lodash": "^4.17.15" - } - }, "grid-index": { "version": "1.1.0" }, @@ -81274,48 +79895,6 @@ } } }, - "hast-util-to-estree": { - "version": "2.3.3", - "dev": true, - "requires": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^2.0.0", - "@types/unist": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "estree-util-attach-comments": "^2.0.0", - "estree-util-is-identifier-name": "^2.0.0", - "hast-util-whitespace": "^2.0.0", - "mdast-util-mdx-expression": "^1.0.0", - "mdast-util-mdxjs-esm": "^1.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-object": "^0.4.1", - "unist-util-position": "^4.0.0", - "zwitch": "^2.0.0" - }, - "dependencies": { - "@types/estree-jsx": { - "version": "1.0.4", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, - "comma-separated-tokens": { - "version": "2.0.3", - "dev": true - }, - "property-information": { - "version": "6.4.1", - "dev": true - }, - "space-separated-tokens": { - "version": "2.0.2", - "dev": true - } - } - }, "hast-util-to-parse5": { "version": "8.0.0", "requires": { @@ -81375,7 +79954,8 @@ } }, "he": { - "version": "1.2.0" + "version": "1.2.0", + "dev": true }, "highlight.js": { "version": "10.7.3" @@ -81455,6 +80035,7 @@ }, "html-minifier-terser": { "version": "6.1.0", + "dev": true, "requires": { "camel-case": "^4.1.2", "clean-css": "^5.2.2", @@ -81466,7 +80047,8 @@ }, "dependencies": { "commander": { - "version": "8.3.0" + "version": "8.3.0", + "dev": true } } }, @@ -81480,6 +80062,7 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", + "dev": true, "requires": { "@types/html-minifier-terser": "^6.0.0", "html-minifier-terser": "^6.0.2", @@ -81489,7 +80072,8 @@ }, "dependencies": { "tapable": { - "version": "2.2.1" + "version": "2.2.1", + "dev": true } } }, @@ -82265,13 +80849,6 @@ "version": "1.0.1", "dev": true }, - "is-reference": { - "version": "3.0.2", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, "is-regex": { "version": "1.1.4", "requires": { @@ -84444,7 +83021,7 @@ }, "json-schema-traverse": { "version": "0.4.1", - "devOptional": true + "dev": true }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -85191,7 +83768,9 @@ } }, "lilconfig": { - "version": "3.0.0", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true }, "lines-and-columns": { @@ -85235,7 +83814,7 @@ }, "loader-runner": { "version": "4.2.0", - "devOptional": true + "dev": true }, "loader-utils": { "version": "1.4.2", @@ -85285,12 +83864,6 @@ "version": "4.0.8", "dev": true }, - "lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", - "dev": true - }, "lodash.escape": { "version": "4.0.1", "dev": true @@ -85338,6 +83911,8 @@ }, "lodash.memoize": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, "lodash.merge": { @@ -85355,6 +83930,8 @@ }, "lodash.uniq": { "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "dev": true }, "log-symbols": { @@ -85399,12 +83976,14 @@ }, "lower-case": { "version": "2.0.2", + "dev": true, "requires": { "tslib": "^2.0.3" }, "dependencies": { "tslib": { - "version": "2.1.0" + "version": "2.1.0", + "dev": true } } }, @@ -85423,6 +84002,7 @@ }, "lru-cache": { "version": "5.1.1", + "devOptional": true, "requires": { "yallist": "^3.0.2" } @@ -85549,10 +84129,6 @@ "resolved": "https://registry.npmjs.org/mapbox-to-css-font/-/mapbox-to-css-font-2.4.5.tgz", "integrity": "sha512-VJ6nB8emkO9VODI0Fk+TQ/0zKBTqmf/Pkt8Xv0kHstoc0iXRajA00DAid4Kc3K5xeFIOoiZrVxijEzj0GLVO2w==" }, - "markdown-extensions": { - "version": "1.1.1", - "dev": true - }, "markdown-table": { "version": "3.0.3" }, @@ -86924,202 +85500,6 @@ } } }, - "mdast-util-mdx": { - "version": "2.0.1", - "dev": true, - "requires": { - "mdast-util-from-markdown": "^1.0.0", - "mdast-util-mdx-expression": "^1.0.0", - "mdast-util-mdx-jsx": "^2.0.0", - "mdast-util-mdxjs-esm": "^1.0.0", - "mdast-util-to-markdown": "^1.0.0" - }, - "dependencies": { - "mdast-util-phrasing": { - "version": "3.0.1", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0", - "unist-util-is": "^5.0.0" - } - }, - "mdast-util-to-markdown": { - "version": "1.5.0", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^3.0.0", - "mdast-util-to-string": "^3.0.0", - "micromark-util-decode-string": "^1.0.0", - "unist-util-visit": "^4.0.0", - "zwitch": "^2.0.0" - } - }, - "mdast-util-to-string": { - "version": "3.2.0", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0" - } - } - } - }, - "mdast-util-mdx-expression": { - "version": "1.3.2", - "dev": true, - "requires": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^2.0.0", - "@types/mdast": "^3.0.0", - "mdast-util-from-markdown": "^1.0.0", - "mdast-util-to-markdown": "^1.0.0" - }, - "dependencies": { - "@types/estree-jsx": { - "version": "1.0.4", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, - "mdast-util-phrasing": { - "version": "3.0.1", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0", - "unist-util-is": "^5.0.0" - } - }, - "mdast-util-to-markdown": { - "version": "1.5.0", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^3.0.0", - "mdast-util-to-string": "^3.0.0", - "micromark-util-decode-string": "^1.0.0", - "unist-util-visit": "^4.0.0", - "zwitch": "^2.0.0" - } - }, - "mdast-util-to-string": { - "version": "3.2.0", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0" - } - } - } - }, - "mdast-util-mdx-jsx": { - "version": "2.1.4", - "dev": true, - "requires": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^2.0.0", - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "ccount": "^2.0.0", - "mdast-util-from-markdown": "^1.1.0", - "mdast-util-to-markdown": "^1.3.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-remove-position": "^4.0.0", - "unist-util-stringify-position": "^3.0.0", - "vfile-message": "^3.0.0" - }, - "dependencies": { - "@types/estree-jsx": { - "version": "1.0.4", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, - "mdast-util-phrasing": { - "version": "3.0.1", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0", - "unist-util-is": "^5.0.0" - } - }, - "mdast-util-to-markdown": { - "version": "1.5.0", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^3.0.0", - "mdast-util-to-string": "^3.0.0", - "micromark-util-decode-string": "^1.0.0", - "unist-util-visit": "^4.0.0", - "zwitch": "^2.0.0" - } - }, - "mdast-util-to-string": { - "version": "3.2.0", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0" - } - } - } - }, - "mdast-util-mdxjs-esm": { - "version": "1.3.1", - "dev": true, - "requires": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^2.0.0", - "@types/mdast": "^3.0.0", - "mdast-util-from-markdown": "^1.0.0", - "mdast-util-to-markdown": "^1.0.0" - }, - "dependencies": { - "@types/estree-jsx": { - "version": "1.0.4", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, - "mdast-util-phrasing": { - "version": "3.0.1", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0", - "unist-util-is": "^5.0.0" - } - }, - "mdast-util-to-markdown": { - "version": "1.5.0", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^3.0.0", - "mdast-util-to-string": "^3.0.0", - "micromark-util-decode-string": "^1.0.0", - "unist-util-visit": "^4.0.0", - "zwitch": "^2.0.0" - } - }, - "mdast-util-to-string": { - "version": "3.2.0", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0" - } - } - } - }, "mdast-util-phrasing": { "version": "4.1.0", "dev": true, @@ -87910,72 +86290,6 @@ } } }, - "micromark-extension-mdx-expression": { - "version": "1.0.8", - "dev": true, - "requires": { - "@types/estree": "^1.0.0", - "micromark-factory-mdx-expression": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-events-to-acorn": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "micromark-extension-mdx-jsx": { - "version": "1.0.5", - "dev": true, - "requires": { - "@types/acorn": "^4.0.0", - "@types/estree": "^1.0.0", - "estree-util-is-identifier-name": "^2.0.0", - "micromark-factory-mdx-expression": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0", - "vfile-message": "^3.0.0" - } - }, - "micromark-extension-mdx-md": { - "version": "1.0.1", - "dev": true, - "requires": { - "micromark-util-types": "^1.0.0" - } - }, - "micromark-extension-mdxjs": { - "version": "1.0.1", - "dev": true, - "requires": { - "acorn": "^8.0.0", - "acorn-jsx": "^5.0.0", - "micromark-extension-mdx-expression": "^1.0.0", - "micromark-extension-mdx-jsx": "^1.0.0", - "micromark-extension-mdx-md": "^1.0.0", - "micromark-extension-mdxjs-esm": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "micromark-extension-mdxjs-esm": { - "version": "1.0.5", - "dev": true, - "requires": { - "@types/estree": "^1.0.0", - "micromark-core-commonmark": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-events-to-acorn": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "unist-util-position-from-estree": "^1.1.0", - "uvu": "^0.5.0", - "vfile-message": "^3.0.0" - } - }, "micromark-factory-destination": { "version": "1.0.0", "requires": { @@ -87993,20 +86307,6 @@ "uvu": "^0.5.0" } }, - "micromark-factory-mdx-expression": { - "version": "1.0.9", - "dev": true, - "requires": { - "@types/estree": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-events-to-acorn": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "unist-util-position-from-estree": "^1.0.0", - "uvu": "^0.5.0", - "vfile-message": "^3.0.0" - } - }, "micromark-factory-space": { "version": "1.0.0", "requires": { @@ -88079,20 +86379,6 @@ "micromark-util-encode": { "version": "1.0.1" }, - "micromark-util-events-to-acorn": { - "version": "1.2.3", - "dev": true, - "requires": { - "@types/acorn": "^4.0.0", - "@types/estree": "^1.0.0", - "@types/unist": "^2.0.0", - "estree-util-visit": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0", - "vfile-message": "^3.0.0" - } - }, "micromark-util-html-tag-name": { "version": "1.1.0" }, @@ -88416,21 +86702,6 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==" }, - "moment-locales-webpack-plugin": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/moment-locales-webpack-plugin/-/moment-locales-webpack-plugin-1.2.0.tgz", - "integrity": "sha512-QAi5v0OlPUP7GXviKMtxnpBAo8WmTHrUNN7iciAhNOEAd9evCOvuN0g1N7ThIg3q11GLCkjY1zQ2saRcf/43nQ==", - "dev": true, - "requires": { - "lodash.difference": "^4.5.0" - } - }, - "moment-timezone": { - "version": "0.5.44", - "requires": { - "moment": "^2.29.4" - } - }, "monaco-editor": { "version": "0.34.1", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.34.1.tgz", @@ -88628,13 +86899,15 @@ }, "no-case": { "version": "3.0.4", + "dev": true, "requires": { "lower-case": "^2.0.2", "tslib": "^2.0.3" }, "dependencies": { "tslib": { - "version": "2.1.0" + "version": "2.1.0", + "dev": true } } }, @@ -88912,7 +87185,8 @@ "node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "devOptional": true }, "nomnom": { "version": "1.8.1", @@ -89100,6 +87374,7 @@ }, "nth-check": { "version": "2.1.1", + "dev": true, "requires": { "boolbase": "^1.0.0" } @@ -90159,13 +88434,15 @@ }, "param-case": { "version": "3.0.4", + "dev": true, "requires": { "dot-case": "^3.0.4", "tslib": "^2.0.3" }, "dependencies": { "tslib": { - "version": "2.6.2" + "version": "2.6.2", + "dev": true } } }, @@ -90194,54 +88471,6 @@ } } }, - "parse-entities": { - "version": "4.0.1", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "character-entities": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "dependencies": { - "character-entities": { - "version": "2.0.2", - "dev": true - }, - "character-entities-legacy": { - "version": "3.0.0", - "dev": true - }, - "character-reference-invalid": { - "version": "2.0.1", - "dev": true - }, - "is-alphabetical": { - "version": "2.0.1", - "dev": true - }, - "is-alphanumerical": { - "version": "2.0.1", - "dev": true, - "requires": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - } - }, - "is-decimal": { - "version": "2.0.1", - "dev": true - }, - "is-hexadecimal": { - "version": "2.0.1", - "dev": true - } - } - }, "parse-headers": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", @@ -90305,13 +88534,15 @@ }, "pascal-case": { "version": "3.1.2", + "dev": true, "requires": { "no-case": "^3.0.4", "tslib": "^2.0.3" }, "dependencies": { "tslib": { - "version": "2.6.2" + "version": "2.6.2", + "dev": true } } }, @@ -90401,15 +88632,6 @@ "version": "2.1.0", "devOptional": true }, - "periscopic": { - "version": "3.1.0", - "dev": true, - "requires": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" - } - }, "picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -90604,12 +88826,14 @@ "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==" }, "postcss": { - "version": "8.4.33", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "dev": true, "requires": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "dependencies": { "nanoid": { @@ -90621,120 +88845,179 @@ } }, "postcss-calc": { - "version": "9.0.1", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.0.2.tgz", + "integrity": "sha512-DT/Wwm6fCKgpYVI7ZEWuPJ4az8hiEHtCUeYjZXqU7Ou4QqYh1Df2yCQ7Ca6N7xqKPFkxN3fhf+u9KSoOCJNAjg==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.11", + "postcss-selector-parser": "^6.1.2", "postcss-value-parser": "^4.2.0" } }, "postcss-colormin": { - "version": "6.0.2", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.2.tgz", + "integrity": "sha512-YntRXNngcvEvDbEjTdRWGU606eZvB5prmHG4BF0yLmVpamXbpsRJzevyy6MZVyuecgzI2AWAlvFi8DAeCqwpvA==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.3", "caniuse-api": "^3.0.0", - "colord": "^2.9.1", + "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" } }, "postcss-convert-values": { - "version": "6.0.2", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.4.tgz", + "integrity": "sha512-e2LSXPqEHVW6aoGbjV9RsSSNDO3A0rZLCBxN24zvxF25WknMPpX8Dm9UxxThyEbaytzggRuZxaGXqaOhxQ514Q==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.3", "postcss-value-parser": "^4.2.0" } }, "postcss-discard-comments": { - "version": "6.0.1", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.3.tgz", + "integrity": "sha512-q6fjd4WU4afNhWOA2WltHgCbkRhZPgQe7cXF74fuVB/ge4QbM9HEaOIzGSiMvM+g/cOsNAUGdf2JDzqA2F8iLA==", "dev": true, - "requires": {} + "requires": { + "postcss-selector-parser": "^6.1.2" + } }, "postcss-discard-duplicates": { - "version": "6.0.1", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.1.tgz", + "integrity": "sha512-oZA+v8Jkpu1ct/xbbrntHRsfLGuzoP+cpt0nJe5ED2FQF8n8bJtn7Bo28jSmBYwqgqnqkuSXJfSUEE7if4nClQ==", "dev": true, "requires": {} }, "postcss-discard-empty": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.0.tgz", + "integrity": "sha512-e+QzoReTZ8IAwhnSdp/++7gBZ/F+nBq9y6PomfwORfP7q9nBpK5AMP64kOt0bA+lShBFbBDcgpJ3X4etHg4lzA==", "dev": true, "requires": {} }, "postcss-discard-overridden": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.0.tgz", + "integrity": "sha512-GmNAzx88u3k2+sBTZrJSDauR0ccpE24omTQCVmaTTZFz1du6AasspjaUPMJ2ud4RslZpoFKyf+6MSPETLojc6w==", "dev": true, "requires": {} }, "postcss-merge-longhand": { - "version": "6.0.2", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.4.tgz", + "integrity": "sha512-zer1KoZA54Q8RVHKOY5vMke0cCdNxMP3KBfDerjH/BYHh4nCIh+1Yy0t1pAEQF18ac/4z3OFclO+ZVH8azjR4A==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.0.2" + "stylehacks": "^7.0.4" } }, "postcss-merge-rules": { - "version": "6.0.3", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.4.tgz", + "integrity": "sha512-ZsaamiMVu7uBYsIdGtKJ64PkcQt6Pcpep/uO90EpLS3dxJi6OXamIobTYcImyXGoW0Wpugh7DSD3XzxZS9JCPg==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.3", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.1", - "postcss-selector-parser": "^6.0.15" + "cssnano-utils": "^5.0.0", + "postcss-selector-parser": "^6.1.2" } }, "postcss-minify-font-values": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.0.tgz", + "integrity": "sha512-2ckkZtgT0zG8SMc5aoNwtm5234eUx1GGFJKf2b1bSp8UflqaeFzR50lid4PfqVI9NtGqJ2J4Y7fwvnP/u1cQog==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-minify-gradients": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.0.tgz", + "integrity": "sha512-pdUIIdj/C93ryCHew0UgBnL2DtUS3hfFa5XtERrs4x+hmpMYGhbzo6l/Ir5de41O0GaKVpK1ZbDNXSY6GkXvtg==", "dev": true, "requires": { - "colord": "^2.9.1", - "cssnano-utils": "^4.0.1", + "colord": "^2.9.3", + "cssnano-utils": "^5.0.0", "postcss-value-parser": "^4.2.0" } }, "postcss-minify-params": { - "version": "6.0.2", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.2.tgz", + "integrity": "sha512-nyqVLu4MFl9df32zTsdcLqCFfE/z2+f8GE1KHPxWOAmegSo6lpV2GNy5XQvrzwbLmiU7d+fYay4cwto1oNdAaQ==", "dev": true, "requires": { - "browserslist": "^4.22.2", - "cssnano-utils": "^4.0.1", + "browserslist": "^4.23.3", + "cssnano-utils": "^5.0.0", "postcss-value-parser": "^4.2.0" } }, "postcss-minify-selectors": { - "version": "6.0.2", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.4.tgz", + "integrity": "sha512-JG55VADcNb4xFCf75hXkzc1rNeURhlo7ugf6JjiiKRfMsKlDzN9CXHZDyiG6x/zGchpjQS+UAgb1d4nqXqOpmA==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.15" + "cssesc": "^3.0.0", + "postcss-selector-parser": "^6.1.2" } }, "postcss-modules-extract-imports": { - "version": "3.0.0", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "requires": {} }, "postcss-modules-local-by-default": { - "version": "4.0.3", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "dev": true, "requires": { "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + } } }, "postcss-modules-scope": { - "version": "3.0.0", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.4" + "postcss-selector-parser": "^7.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + } } }, "postcss-modules-values": { @@ -90745,92 +89028,118 @@ } }, "postcss-normalize-charset": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.0.tgz", + "integrity": "sha512-ABisNUXMeZeDNzCQxPxBCkXexvBrUHV+p7/BXOY+ulxkcjUZO0cp8ekGBwvIh2LbCwnWbyMPNJVtBSdyhM2zYQ==", "dev": true, "requires": {} }, "postcss-normalize-display-values": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.0.tgz", + "integrity": "sha512-lnFZzNPeDf5uGMPYgGOw7v0BfB45+irSRz9gHQStdkkhiM0gTfvWkWB5BMxpn0OqgOQuZG/mRlZyJxp0EImr2Q==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-positions": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.0.tgz", + "integrity": "sha512-I0yt8wX529UKIGs2y/9Ybs2CelSvItfmvg/DBIjTnoUSrPxSV7Z0yZ8ShSVtKNaV/wAY+m7bgtyVQLhB00A1NQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-repeat-style": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.0.tgz", + "integrity": "sha512-o3uSGYH+2q30ieM3ppu9GTjSXIzOrRdCUn8UOMGNw7Af61bmurHTWI87hRybrP6xDHvOe5WlAj3XzN6vEO8jLw==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-string": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.0.tgz", + "integrity": "sha512-w/qzL212DFVOpMy3UGyxrND+Kb0fvCiBBujiaONIihq7VvtC7bswjWgKQU/w4VcRyDD8gpfqUiBQ4DUOwEJ6Qg==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-timing-functions": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.0.tgz", + "integrity": "sha512-tNgw3YV0LYoRwg43N3lTe3AEWZ66W7Dh7lVEpJbHoKOuHc1sLrzMLMFjP8SNULHaykzsonUEDbKedv8C+7ej6g==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-unicode": { - "version": "6.0.2", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.2.tgz", + "integrity": "sha512-ztisabK5C/+ZWBdYC+Y9JCkp3M9qBv/XFvDtSw0d/XwfT3UaKeW/YTm/MD/QrPNxuecia46vkfEhewjwcYFjkg==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.3", "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-url": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.0.tgz", + "integrity": "sha512-+d7+PpE+jyPX1hDQZYG+NaFD+Nd2ris6r8fPTBAjE8z/U41n/bib3vze8x7rKs5H1uEw5ppe9IojewouHk0klQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-whitespace": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.0.tgz", + "integrity": "sha512-37/toN4wwZErqohedXYqWgvcHUGlT8O/m2jVkAfAe9Bd4MzRqlBmXrJRePH0e9Wgnz2X7KymTgTOaaFizQe3AQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-ordered-values": { - "version": "6.0.1", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.1.tgz", + "integrity": "sha512-irWScWRL6nRzYmBOXReIKch75RRhNS86UPUAxXdmW/l0FcAsg0lvAXQCby/1lymxn/o0gVa6Rv/0f03eJOwHxw==", "dev": true, "requires": { - "cssnano-utils": "^4.0.1", + "cssnano-utils": "^5.0.0", "postcss-value-parser": "^4.2.0" } }, "postcss-reduce-initial": { - "version": "6.0.2", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.2.tgz", + "integrity": "sha512-pOnu9zqQww7dEKf62Nuju6JgsW2V0KRNBHxeKohU+JkHd/GAH5uvoObqFLqkeB2n20mr6yrlWDvo5UBU5GnkfA==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.3", "caniuse-api": "^3.0.0" } }, "postcss-reduce-transforms": { - "version": "6.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.0.tgz", + "integrity": "sha512-pnt1HKKZ07/idH8cpATX/ujMbtOGhUfE+m8gbqwJE05aTaNw8gbo34a2e3if0xc0dlu75sUOiqvwCGY3fzOHew==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-selector-parser": { - "version": "6.0.15", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "devOptional": true, "requires": { "cssesc": "^3.0.0", @@ -90838,18 +89147,22 @@ } }, "postcss-svgo": { - "version": "6.0.2", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.0.1.tgz", + "integrity": "sha512-0WBUlSL4lhD9rA5k1e5D8EN5wCEyZD6HJk0jIvRxl+FDVOMlJ7DePHYWGGVc5QRqrJ3/06FTXM0bxjmJpmTPSA==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" + "svgo": "^3.3.2" } }, "postcss-unique-selectors": { - "version": "6.0.2", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.3.tgz", + "integrity": "sha512-J+58u5Ic5T1QjP/LDV9g3Cx4CNOgB5vz+kM6+OxHHhFACdcDeKhBXjQmB7fnIZM12YSTvsL0Opwco83DmacW2g==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.15" + "postcss-selector-parser": "^6.1.2" } }, "postcss-value-parser": { @@ -90895,6 +89208,7 @@ }, "pretty-error": { "version": "4.0.0", + "dev": true, "requires": { "lodash": "^4.17.20", "renderkid": "^3.0.0" @@ -91365,7 +89679,7 @@ }, "randombytes": { "version": "2.1.0", - "devOptional": true, + "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -91613,28 +89927,6 @@ "classnames": "^2.2.1" } }, - "rc-picker": { - "version": "4.5.0", - "requires": { - "@babel/runtime": "^7.10.1", - "@rc-component/trigger": "^2.0.0", - "classnames": "^2.2.1", - "rc-overflow": "^1.3.2", - "rc-resize-observer": "^1.4.0", - "rc-util": "^5.38.1" - }, - "dependencies": { - "rc-resize-observer": { - "version": "1.4.0", - "requires": { - "@babel/runtime": "^7.20.7", - "classnames": "^2.2.1", - "rc-util": "^5.38.0", - "resize-observer-polyfill": "^1.5.1" - } - } - } - }, "rc-progress": { "version": "3.1.1", "requires": { @@ -92102,24 +90394,6 @@ } } }, - "react-move": { - "version": "2.9.1", - "requires": { - "@babel/runtime": "^7.2.0", - "d3-interpolate": "^1.3.2", - "d3-timer": "^1.0.9", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" - }, - "dependencies": { - "d3-interpolate": { - "version": "1.4.0", - "requires": { - "d3-color": "^3.1.0" - } - } - } - }, "react-redux": { "version": "7.2.9", "requires": { @@ -93013,7 +91287,8 @@ } }, "relateurl": { - "version": "0.2.7" + "version": "0.2.7", + "dev": true }, "release-zalgo": { "version": "1.0.0", @@ -93441,6 +91716,7 @@ }, "renderkid": { "version": "3.0.0", + "dev": true, "requires": { "css-select": "^4.1.3", "dom-converter": "^0.2.0", @@ -93451,6 +91727,7 @@ "dependencies": { "css-select": { "version": "4.3.0", + "dev": true, "requires": { "boolbase": "^1.0.0", "css-what": "^6.0.1", @@ -93461,6 +91738,7 @@ }, "dom-serializer": { "version": "1.4.1", + "dev": true, "requires": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", @@ -93469,12 +91747,14 @@ }, "domhandler": { "version": "4.3.1", + "dev": true, "requires": { "domelementtype": "^2.2.0" } }, "domutils": { "version": "2.8.0", + "dev": true, "requires": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", @@ -93482,10 +91762,12 @@ } }, "entities": { - "version": "2.2.0" + "version": "2.2.0", + "dev": true }, "htmlparser2": { "version": "6.1.0", + "dev": true, "requires": { "domelementtype": "^2.0.1", "domhandler": "^4.0.0", @@ -93908,7 +92190,7 @@ }, "schema-utils": { "version": "3.3.0", - "devOptional": true, + "dev": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -94006,7 +92288,7 @@ }, "serialize-javascript": { "version": "6.0.2", - "devOptional": true, + "dev": true, "requires": { "randombytes": "^2.1.0" } @@ -94556,7 +92838,9 @@ "version": "0.7.4" }, "source-map-js": { - "version": "1.0.2", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, "source-map-resolve": { @@ -94572,13 +92856,15 @@ }, "source-map-support": { "version": "0.5.21", + "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" }, "dependencies": { "source-map": { - "version": "0.6.1" + "version": "0.6.1", + "dev": true } } }, @@ -95086,20 +93372,6 @@ "es-object-atoms": "^1.0.0" } }, - "stringify-entities": { - "version": "4.0.3", - "dev": true, - "requires": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "dependencies": { - "character-entities-legacy": { - "version": "3.0.0", - "dev": true - } - } - }, "strip-ansi": { "version": "6.0.1", "requires": { @@ -95198,11 +93470,13 @@ } }, "stylehacks": { - "version": "6.0.2", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.4.tgz", + "integrity": "sha512-i4zfNrGMt9SB4xRK9L83rlsFCgdGANfeDAYacO1pkqcE7cRHPdWHwnKZVz7WY17Veq/FvyYsRAU++Ga+qDFIww==", "dev": true, "requires": { - "browserslist": "^4.22.2", - "postcss-selector-parser": "^6.0.15" + "browserslist": "^4.23.3", + "postcss-selector-parser": "^6.1.2" } }, "stylis": { @@ -95233,7 +93507,9 @@ "optional": true }, "svgo": { - "version": "3.2.0", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", "dev": true, "requires": { "@trysound/sax": "0.2.0", @@ -95473,6 +93749,7 @@ }, "terser": { "version": "5.27.0", + "dev": true, "requires": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -95482,7 +93759,7 @@ }, "terser-webpack-plugin": { "version": "5.3.10", - "devOptional": true, + "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", @@ -95493,11 +93770,11 @@ "dependencies": { "has-flag": { "version": "4.0.0", - "devOptional": true + "dev": true }, "jest-worker": { "version": "27.5.1", - "devOptional": true, + "dev": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -95506,7 +93783,7 @@ }, "supports-color": { "version": "8.1.1", - "devOptional": true, + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -95942,7 +94219,7 @@ }, "tslib": { "version": "1.11.1", - "devOptional": true + "dev": true }, "tsutils": { "version": "3.21.0", @@ -96223,21 +94500,6 @@ "@types/unist": "^2.0.0" } }, - "unist-util-position-from-estree": { - "version": "1.1.2", - "dev": true, - "requires": { - "@types/unist": "^2.0.0" - } - }, - "unist-util-remove-position": { - "version": "4.0.2", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "unist-util-visit": "^4.0.0" - } - }, "unist-util-stringify-position": { "version": "3.0.2", "requires": { @@ -96330,6 +94592,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "devOptional": true, "requires": { "escalade": "^3.2.0", "picocolors": "^1.1.0" @@ -96449,7 +94712,8 @@ } }, "utila": { - "version": "0.4.0" + "version": "0.4.0", + "dev": true }, "utils-merge": { "version": "1.0.1" @@ -96704,7 +94968,7 @@ }, "watchpack": { "version": "2.4.1", - "devOptional": true, + "dev": true, "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -96772,7 +95036,7 @@ "version": "5.97.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", - "devOptional": true, + "dev": true, "requires": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -96801,7 +95065,7 @@ "dependencies": { "enhanced-resolve": { "version": "5.17.1", - "devOptional": true, + "dev": true, "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -96809,7 +95073,7 @@ }, "tapable": { "version": "2.2.1", - "devOptional": true + "dev": true } } }, @@ -97135,7 +95399,7 @@ }, "webpack-sources": { "version": "3.2.3", - "devOptional": true + "dev": true }, "webpack-virtual-modules": { "version": "0.5.0", @@ -97615,49 +95879,6 @@ "dev": true, "requires": {} }, - "xdm": { - "version": "3.4.0", - "dev": true, - "requires": { - "@rollup/pluginutils": "^4.0.0", - "@types/estree-jsx": "^0.0.1", - "@types/mdx": "^2.0.0", - "astring": "^1.6.0", - "deasync": "^0.1.0", - "estree-util-build-jsx": "^2.0.0", - "estree-util-is-identifier-name": "^2.0.0", - "estree-walker": "^3.0.0", - "hast-util-to-estree": "^2.0.0", - "markdown-extensions": "^1.0.0", - "mdast-util-mdx": "^2.0.0", - "micromark-extension-mdxjs": "^1.0.0", - "node-fetch": "^3.2.0", - "periscopic": "^3.0.0", - "remark-parse": "^10.0.0", - "remark-rehype": "^10.0.0", - "source-map": "^0.7.0", - "unified": "^10.0.0", - "unist-util-position-from-estree": "^1.0.0", - "unist-util-stringify-position": "^3.0.0", - "unist-util-visit": "^4.0.0", - "vfile": "^5.0.0" - }, - "dependencies": { - "data-uri-to-buffer": { - "version": "4.0.1", - "dev": true - }, - "node-fetch": { - "version": "3.3.2", - "dev": true, - "requires": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - } - } - } - }, "xml-name-validator": { "version": "3.0.0", "dev": true @@ -97691,7 +95912,8 @@ "peer": true }, "yallist": { - "version": "3.1.1" + "version": "3.1.1", + "devOptional": true }, "yaml": { "version": "1.10.2" diff --git a/superset-frontend/package.json b/superset-frontend/package.json index bc2d833468789..a71e64ac80f60 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -97,17 +97,12 @@ "@superset-ui/legacy-plugin-chart-calendar": "file:./plugins/legacy-plugin-chart-calendar", "@superset-ui/legacy-plugin-chart-chord": "file:./plugins/legacy-plugin-chart-chord", "@superset-ui/legacy-plugin-chart-country-map": "file:./plugins/legacy-plugin-chart-country-map", - "@superset-ui/legacy-plugin-chart-event-flow": "file:./plugins/legacy-plugin-chart-event-flow", - "@superset-ui/legacy-plugin-chart-heatmap": "file:./plugins/legacy-plugin-chart-heatmap", - "@superset-ui/legacy-plugin-chart-histogram": "file:./plugins/legacy-plugin-chart-histogram", "@superset-ui/legacy-plugin-chart-horizon": "file:./plugins/legacy-plugin-chart-horizon", "@superset-ui/legacy-plugin-chart-map-box": "file:./plugins/legacy-plugin-chart-map-box", "@superset-ui/legacy-plugin-chart-paired-t-test": "file:./plugins/legacy-plugin-chart-paired-t-test", "@superset-ui/legacy-plugin-chart-parallel-coordinates": "file:./plugins/legacy-plugin-chart-parallel-coordinates", "@superset-ui/legacy-plugin-chart-partition": "file:./plugins/legacy-plugin-chart-partition", "@superset-ui/legacy-plugin-chart-rose": "file:./plugins/legacy-plugin-chart-rose", - "@superset-ui/legacy-plugin-chart-sankey": "file:./plugins/legacy-plugin-chart-sankey", - "@superset-ui/legacy-plugin-chart-sankey-loop": "file:./plugins/legacy-plugin-chart-sankey-loop", "@superset-ui/legacy-plugin-chart-world-map": "file:./plugins/legacy-plugin-chart-world-map", "@superset-ui/legacy-preset-chart-deckgl": "file:./plugins/legacy-preset-chart-deckgl", "@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3", @@ -130,7 +125,6 @@ "ace-builds": "^1.36.3", "antd": "4.10.3", "antd-v5": "npm:antd@^5.18.0", - "babel-plugin-typescript-to-proptypes": "^2.0.0", "bootstrap": "^3.4.1", "brace": "^0.11.1", "chrono-node": "^2.7.6", @@ -152,7 +146,6 @@ "geostyler-style": "^7.5.0", "geostyler-wfs-parser": "^2.0.3", "googleapis": "^130.0.0", - "html-webpack-plugin": "^5.6.3", "immer": "^10.1.1", "interweave": "^13.1.0", "jquery": "^3.7.1", @@ -166,8 +159,6 @@ "markdown-to-jsx": "^7.7.2", "match-sorter": "^6.3.4", "memoize-one": "^5.2.1", - "moment": "^2.30.1", - "moment-timezone": "^0.5.44", "mousetrap": "^1.6.5", "mustache": "^4.2.0", "nanoid": "^5.0.9", @@ -296,10 +287,11 @@ "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-jsx-remove-data-test-id": "^3.0.0", "babel-plugin-lodash": "^3.3.4", + "babel-plugin-typescript-to-proptypes": "^2.0.0", "copy-webpack-plugin": "^12.0.2", "cross-env": "^7.0.3", - "css-loader": "^6.8.1", - "css-minimizer-webpack-plugin": "^5.0.1", + "css-loader": "^7.1.2", + "css-minimizer-webpack-plugin": "^7.0.0", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.7", "esbuild": "^0.20.0", @@ -328,6 +320,7 @@ "fetch-mock": "^7.7.3", "fork-ts-checker-webpack-plugin": "^9.0.2", "history": "^5.3.0", + "html-webpack-plugin": "^5.6.3", "ignore-styles": "^5.0.1", "imports-loader": "^5.0.0", "jest": "^29.7.0", @@ -342,8 +335,6 @@ "less-loader": "^12.2.0", "mini-css-extract-plugin": "^2.9.0", "mock-socket": "^9.3.1", - "moment-locales-webpack-plugin": "^1.2.0", - "node-fetch": "^2.6.7", "open-cli": "^8.0.0", "po2json": "^0.4.5", "prettier": "3.3.3", @@ -368,8 +359,7 @@ "webpack-dev-server": "^4.15.1", "webpack-manifest-plugin": "^5.0.0", "webpack-sources": "^3.2.3", - "webpack-visualizer-plugin2": "^1.1.0", - "xdm": "^3.4.0" + "webpack-visualizer-plugin2": "^1.1.0" }, "engines": { "node": "^20.16.0", diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/constants.ts b/superset-frontend/packages/superset-ui-chart-controls/src/constants.ts index 6534258c66f56..c9e8bf9db7fd2 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/constants.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/constants.ts @@ -81,5 +81,5 @@ export const DEFAULT_XAXIS_SORT_SERIES_DATA: SortSeriesData = { export const DEFAULT_DATE_PATTERN = /\d{4}-\d{2}-\d{2}/g; -// When moment fails to parse a date +// When it fails to parse a date export const INVALID_DATE = 'Invalid date'; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/operators/utils/timeOffset.ts b/superset-frontend/packages/superset-ui-chart-controls/src/operators/utils/timeOffset.ts index 8a7d9a964f8b8..852b4eb1ab830 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/operators/utils/timeOffset.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/operators/utils/timeOffset.ts @@ -18,7 +18,6 @@ * under the License. */ import { JsonObject } from '@superset-ui/core'; -import { isString } from 'lodash'; export const getTimeOffset = ( series: JsonObject, @@ -36,7 +35,9 @@ export const hasTimeOffset = ( series: JsonObject, timeCompare: string[], ): boolean => - isString(series.name) ? !!getTimeOffset(series, timeCompare) : false; + typeof series.name === 'string' + ? !!getTimeOffset(series, timeCompare) + : false; export const getOriginalSeries = ( seriesName: string, diff --git a/superset-frontend/packages/superset-ui-chart-controls/test/operators/histogramOperator.test.ts b/superset-frontend/packages/superset-ui-chart-controls/test/operators/histogramOperator.test.ts index b4c6eacc94190..b4f76f42016f6 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/test/operators/histogramOperator.test.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/test/operators/histogramOperator.test.ts @@ -26,7 +26,7 @@ const formData: SqlaFormData = { cumulative: true, normalize: true, groupby: ['country', 'region'], - viz_type: VizType.LegacyHistogram, + viz_type: VizType.Histogram, datasource: 'foo', }; diff --git a/superset-frontend/packages/superset-ui-core/src/chart/types/VizType.ts b/superset-frontend/packages/superset-ui-core/src/chart/types/VizType.ts index ec01b07665582..110d44f8fa203 100644 --- a/superset-frontend/packages/superset-ui-core/src/chart/types/VizType.ts +++ b/superset-frontend/packages/superset-ui-core/src/chart/types/VizType.ts @@ -30,8 +30,6 @@ export enum VizType { Chord = 'chord', Compare = 'compare', CountryMap = 'country_map', - DistBar = 'dist_bar', - EventFlow = 'event_flow', Funnel = 'funnel', Gauge = 'gauge_chart', Graph = 'graph_chart', @@ -39,13 +37,7 @@ export enum VizType { Heatmap = 'heatmap_v2', Histogram = 'histogram_v2', Horizon = 'horizon', - LegacyArea = 'area', - LegacyBar = 'bar', LegacyBubble = 'bubble', - LegacyHeatmap = 'heatmap', - LegacyHistogram = 'histogram', - LegacyLine = 'line', - LegacySankey = 'sankey', Line = 'echarts_timeseries_line', MapBox = 'mapbox', MixedTimeseries = 'mixed_timeseries', diff --git a/superset-frontend/packages/superset-ui-core/src/color/CategoricalColorScale.ts b/superset-frontend/packages/superset-ui-core/src/color/CategoricalColorScale.ts index 707ae3d4afd66..53a5b855bf87e 100644 --- a/superset-frontend/packages/superset-ui-core/src/color/CategoricalColorScale.ts +++ b/superset-frontend/packages/superset-ui-core/src/color/CategoricalColorScale.ts @@ -65,7 +65,7 @@ class CategoricalColorScale extends ExtensibleFunction { ); // holds original color scheme colors this.originColors = colors; - // holds the extended color range (includes analagous colors) + // holds the extended color range (includes analogous colors) this.colors = colors; // holds the values of this specific slice (label+color) this.chartLabelsColorMap = new Map(); @@ -139,7 +139,7 @@ class CategoricalColorScale extends ExtensibleFunction { // a forced color will always be used independently of the usage count if (!forcedColor && !isExistingLabel) { - if (isFeatureEnabled(FeatureFlag.UseAnalagousColors)) { + if (isFeatureEnabled(FeatureFlag.UseAnalogousColors)) { this.incrementColorRange(); } if ( diff --git a/superset-frontend/packages/superset-ui-core/src/components/SafeMarkdown.tsx b/superset-frontend/packages/superset-ui-core/src/components/SafeMarkdown.tsx index 02db7dadbc63e..d35c2160e9784 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/SafeMarkdown.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/SafeMarkdown.tsx @@ -19,7 +19,7 @@ import { useEffect, useMemo, useState } from 'react'; import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'; import remarkGfm from 'remark-gfm'; -import { mergeWith, isArray } from 'lodash'; +import { mergeWith } from 'lodash'; import { FeatureFlag, isFeatureEnabled } from '../utils'; interface SafeMarkdownProps { @@ -33,7 +33,7 @@ export function getOverrideHtmlSchema( htmlSchemaOverrides: SafeMarkdownProps['htmlSchemaOverrides'], ) { return mergeWith(originalSchema, htmlSchemaOverrides, (objValue, srcValue) => - isArray(objValue) ? objValue.concat(srcValue) : undefined, + Array.isArray(objValue) ? objValue.concat(srcValue) : undefined, ); } diff --git a/superset-frontend/packages/superset-ui-core/src/query/normalizeOrderBy.ts b/superset-frontend/packages/superset-ui-core/src/query/normalizeOrderBy.ts index b07338781c748..840cf4c1a180b 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/normalizeOrderBy.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/normalizeOrderBy.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { isEmpty, isBoolean } from 'lodash'; +import { isEmpty } from 'lodash'; import { QueryObject } from './types'; @@ -30,7 +30,7 @@ export default function normalizeOrderBy( Array.isArray(orderbyClause) && orderbyClause.length === 2 && !isEmpty(orderbyClause[0]) && - isBoolean(orderbyClause[1]) + typeof orderbyClause[1] === 'boolean' ) { return queryObject; } diff --git a/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts b/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts index e20d94c7d8549..34e8d51f27fd7 100644 --- a/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts +++ b/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts @@ -52,13 +52,12 @@ export enum FeatureFlag { HorizontalFilterBar = 'HORIZONTAL_FILTER_BAR', ListviewsDefaultCardView = 'LISTVIEWS_DEFAULT_CARD_VIEW', ScheduledQueries = 'SCHEDULED_QUERIES', - ShareQueriesViaKvStore = 'SHARE_QUERIES_VIA_KV_STORE', SqllabBackendPersistence = 'SQLLAB_BACKEND_PERSISTENCE', SqlValidatorsByEngine = 'SQL_VALIDATORS_BY_ENGINE', SshTunneling = 'SSH_TUNNELING', TaggingSystem = 'TAGGING_SYSTEM', Thumbnails = 'THUMBNAILS', - UseAnalagousColors = 'USE_ANALAGOUS_COLORS', + UseAnalogousColors = 'USE_ANALOGOUS_COLORS', ForceSqlLabRunAsync = 'SQLLAB_FORCE_RUN_ASYNC', SlackEnableAvatars = 'SLACK_ENABLE_AVATARS', EnableDashboardScreenshotEndpoints = 'ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS', diff --git a/superset-frontend/packages/superset-ui-core/test/chart/clients/ChartClient.test.ts b/superset-frontend/packages/superset-ui-core/test/chart/clients/ChartClient.test.ts index 50238d3d7dc47..f34c4b84e84b4 100644 --- a/superset-frontend/packages/superset-ui-core/test/chart/clients/ChartClient.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/chart/clients/ChartClient.test.ts @@ -87,13 +87,13 @@ describe('ChartClient', () => { sliceId, formData: { granularity: 'second', - viz_type: VizType.LegacyBar, + viz_type: VizType.Bar, }, }), ).resolves.toEqual({ ...sankeyFormData, granularity: 'second', - viz_type: VizType.LegacyBar, + viz_type: VizType.Bar, }); }); it('returns promise of formData if only formData was given', () => @@ -102,13 +102,13 @@ describe('ChartClient', () => { formData: { datasource: '1__table', granularity: 'minute', - viz_type: VizType.LegacyLine, + viz_type: VizType.Line, }, }), ).resolves.toEqual({ datasource: '1__table', granularity: 'minute', - viz_type: VizType.LegacyLine, + viz_type: VizType.Line, })); it('rejects if none of sliceId or formData is specified', () => expect( @@ -256,7 +256,7 @@ describe('ChartClient', () => { it('loadAllDataNecessaryForAChart', () => { fetchMock.get(`glob:*/api/v1/form_data/?slice_id=${sliceId}`, { granularity: 'minute', - viz_type: VizType.LegacyLine, + viz_type: VizType.Line, datasource: '1__table', color: 'living-coral', }); @@ -276,12 +276,12 @@ describe('ChartClient', () => { }); getChartMetadataRegistry().registerValue( - VizType.LegacyLine, + VizType.Line, new ChartMetadata({ name: 'Line', thumbnail: '.gif' }), ); getChartBuildQueryRegistry().registerValue( - VizType.LegacyLine, + VizType.Line, (formData: QueryFormData) => buildQueryContext(formData), ); @@ -297,7 +297,7 @@ describe('ChartClient', () => { }, formData: { granularity: 'minute', - viz_type: VizType.LegacyLine, + viz_type: VizType.Line, datasource: '1__table', color: 'living-coral', }, diff --git a/superset-frontend/packages/superset-ui-core/test/chart/fixtures/formData.ts b/superset-frontend/packages/superset-ui-core/test/chart/fixtures/formData.ts index 85bfd238ec6e8..ef8baab7f2dce 100644 --- a/superset-frontend/packages/superset-ui-core/test/chart/fixtures/formData.ts +++ b/superset-frontend/packages/superset-ui-core/test/chart/fixtures/formData.ts @@ -71,7 +71,7 @@ export const sunburstFormData = { export const sankeyFormData = { datasource: '1__table', - viz_type: VizType.LegacySankey, + viz_type: VizType.Sankey, slice_id: 1, url_params: {}, granularity_sqla: null, diff --git a/superset-frontend/packages/superset-ui-core/test/color/CategoricalColorScale.test.ts b/superset-frontend/packages/superset-ui-core/test/color/CategoricalColorScale.test.ts index 9ba4bcc5b01a4..dbd72881a706f 100644 --- a/superset-frontend/packages/superset-ui-core/test/color/CategoricalColorScale.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/color/CategoricalColorScale.test.ts @@ -165,7 +165,7 @@ describe('CategoricalColorScale', () => { }); it('get analogous colors when number of items exceed available colors', () => { window.featureFlags = { - [FeatureFlag.UseAnalagousColors]: true, + [FeatureFlag.UseAnalogousColors]: true, }; const scale = new CategoricalColorScale(['blue', 'red', 'green']); scale.getColor('pig'); diff --git a/superset-frontend/packages/superset-ui-core/test/color/LabelsColorMapSingleton.test.ts b/superset-frontend/packages/superset-ui-core/test/color/LabelsColorMapSingleton.test.ts index 24521d2d9dbb0..c4ac0e4ff31a2 100644 --- a/superset-frontend/packages/superset-ui-core/test/color/LabelsColorMapSingleton.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/color/LabelsColorMapSingleton.test.ts @@ -212,7 +212,7 @@ describe('LabelsColorMap', () => { it('should use recycle colors', () => { window.featureFlags = { - [FeatureFlag.UseAnalagousColors]: false, + [FeatureFlag.UseAnalogousColors]: false, }; labelsColorMap.addSlice('a', 'red', 1); labelsColorMap.addSlice('b', 'blue', 2); @@ -224,9 +224,9 @@ describe('LabelsColorMap', () => { expect(getAnalogousColorsSpy).not.toHaveBeenCalled(); }); - it('should use analagous colors', () => { + it('should use analogous colors', () => { window.featureFlags = { - [FeatureFlag.UseAnalagousColors]: true, + [FeatureFlag.UseAnalogousColors]: true, }; labelsColorMap.addSlice('a', 'red', 1); diff --git a/superset-frontend/packages/superset-ui-core/test/query/api/legacy/getFormData.test.ts b/superset-frontend/packages/superset-ui-core/test/query/api/legacy/getFormData.test.ts index 5d46f1a35ccf4..4987d8b91d659 100644 --- a/superset-frontend/packages/superset-ui-core/test/query/api/legacy/getFormData.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/query/api/legacy/getFormData.test.ts @@ -29,7 +29,7 @@ describe('getFormData()', () => { const mockData = { datasource: '1__table', - viz_type: VizType.LegacySankey, + viz_type: VizType.Sankey, slice_id: 1, url_params: {}, granularity_sqla: null, diff --git a/superset-frontend/packages/superset-ui-demo/package.json b/superset-frontend/packages/superset-ui-demo/package.json index 75125c1946cb3..4a6533e45cc77 100644 --- a/superset-frontend/packages/superset-ui-demo/package.json +++ b/superset-frontend/packages/superset-ui-demo/package.json @@ -70,17 +70,12 @@ "@superset-ui/legacy-plugin-chart-calendar": "*", "@superset-ui/legacy-plugin-chart-chord": "*", "@superset-ui/legacy-plugin-chart-country-map": "*", - "@superset-ui/legacy-plugin-chart-event-flow": "*", - "@superset-ui/legacy-plugin-chart-heatmap": "*", - "@superset-ui/legacy-plugin-chart-histogram": "*", "@superset-ui/legacy-plugin-chart-horizon": "*", "@superset-ui/legacy-plugin-chart-map-box": "*", "@superset-ui/legacy-plugin-chart-paired-t-test": "*", "@superset-ui/legacy-plugin-chart-parallel-coordinates": "*", "@superset-ui/legacy-plugin-chart-partition": "*", "@superset-ui/legacy-plugin-chart-rose": "*", - "@superset-ui/legacy-plugin-chart-sankey": "*", - "@superset-ui/legacy-plugin-chart-sankey-loop": "*", "@superset-ui/legacy-plugin-chart-time-table": "*", "@superset-ui/legacy-plugin-chart-world-map": "*", "@superset-ui/legacy-preset-chart-deckgl": "*", diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-event-flow/EventFlow.stories.jsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-event-flow/EventFlow.stories.jsx deleted file mode 100644 index daac4ce8e43c1..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-event-flow/EventFlow.stories.jsx +++ /dev/null @@ -1,50 +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 { SuperChart } from '@superset-ui/core'; -import sampleEvents from '@data-ui/event-flow/build/sampleEvents'; -import EventFlowChartPlugin from '@superset-ui/legacy-plugin-chart-event-flow'; - -new EventFlowChartPlugin().configure({ key: 'event-flow' }).register(); - -export default { - title: 'Legacy Chart Plugins/legacy-plugin-chart-event-flow', -}; - -const data = sampleEvents.twentyUsers.allEvents.map( - ({ ENTITY_ID, EVENT_NAME, TS }) => ({ - __timestamp: TS, - eventName: EVENT_NAME, - userId: ENTITY_ID, - }), -); - -export const basic = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-heatmap/Heatmap.stories.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-heatmap/Heatmap.stories.tsx deleted file mode 100644 index c45ea21a503ea..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-heatmap/Heatmap.stories.tsx +++ /dev/null @@ -1,147 +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 { SuperChart, VizType } from '@superset-ui/core'; -import HeatmapChartPlugin from '@superset-ui/legacy-plugin-chart-heatmap'; -import ResizableChartDemo from '../../../shared/components/ResizableChartDemo'; -import data from './data'; - -new HeatmapChartPlugin().configure({ key: VizType.LegacyHeatmap }).register(); - -export default { - title: 'Legacy Chart Plugins/legacy-plugin-chart-heatmap', -}; - -export const basic = () => ( - -); - -export const resizable = () => ( - - {({ width, height }) => ( - - )} - -); - -export const withNullData = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-heatmap/data.ts b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-heatmap/data.ts deleted file mode 100644 index 4a1a6f6f3c7f3..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-heatmap/data.ts +++ /dev/null @@ -1,182 +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. - */ - -export default [ - { - x: 'Energy', - y: 'Electricity and heat', - v: 24.9, - perc: 1.0, - rank: 1.0, - }, - { - x: 'Energy', - y: 'Industry', - v: 14.7, - perc: 0.5887096774193549, - rank: 0.9882352941176471, - }, - { - x: 'Energy', - y: 'Transportation', - v: 14.3, - perc: 0.5725806451612905, - rank: 0.9764705882352941, - }, - { - x: 'Deforestation', - y: 'Carbon Dioxide', - v: 10.9, - perc: 0.435483870967742, - rank: 0.9588235294117647, - }, - { - x: 'Land Use Change', - y: 'Deforestation', - v: 10.9, - perc: 0.435483870967742, - rank: 0.9588235294117647, - }, - { - x: 'Road', - y: 'Carbon Dioxide', - v: 10.5, - perc: 0.4193548387096775, - rank: 0.9352941176470588, - }, - { - x: 'Transportation', - y: 'Road', - v: 10.5, - perc: 0.4193548387096775, - rank: 0.9352941176470588, - }, - { - x: 'Residential Buildings', - y: 'Carbon Dioxide', - v: 10.2, - perc: 0.40725806451612906, - rank: 0.9176470588235294, - }, - { - x: 'Energy', - y: 'Other Fuel Combustion', - v: 8.6, - perc: 0.342741935483871, - rank: 0.9058823529411765, - }, - { - x: 'Other Industry', - y: 'Carbon Dioxide', - v: 6.6, - perc: 0.26209677419354843, - rank: 0.8941176470588236, - }, - { - x: 'Commercial Buildings', - y: 'Carbon Dioxide', - v: 6.3, - perc: 0.25000000000000006, - rank: 0.8823529411764706, - }, - { - x: 'Agriculture', - y: 'Livestock and Manure', - v: 5.4, - perc: 0.2137096774193549, - rank: 0.8705882352941177, - }, - { - x: 'Agriculture', - y: 'Agriculture Soils', - v: 5.2, - perc: 0.20564516129032262, - rank: 0.8470588235294118, - }, - { - x: 'Agriculture Soils', - y: 'Nitrous Oxide', - v: 5.2, - perc: 0.20564516129032262, - rank: 0.8470588235294118, - }, - { - x: 'Oil and Gas Processing', - y: 'Methane', - v: 2.8, - perc: 0.10887096774193548, - rank: 0.6705882352941176, - }, - { - x: 'Electricity and heat', - y: 'Other Industry', - v: 2.7, - perc: 0.10483870967741937, - rank: 0.6470588235294118, - }, - { - x: 'Rail - Ship and Other Transport', - y: 'Carbon Dioxide', - v: 2.5, - perc: 0.09677419354838711, - rank: 0.6294117647058823, - }, - { - x: 'Transportation', - y: 'Rail - Ship and Other Transport', - v: 2.5, - perc: 0.09677419354838711, - rank: 0.6294117647058823, - }, - { - x: 'Electricity and heat', - y: 'T and D Losses', - v: 2.2, - perc: 0.08467741935483872, - rank: 0.6058823529411764, - }, - { - x: 'T and D Losses', - y: 'Carbon Dioxide', - v: 2.2, - perc: 0.08467741935483872, - rank: 0.6058823529411764, - }, - { - x: 'Electricity and heat', - y: 'Unallocated Fuel Combustion', - v: 2.0, - perc: 0.07661290322580645, - rank: 0.5882352941176471, - }, - { - x: 'Industry', - y: 'Cement', - v: 1.9, - perc: 0.07258064516129033, - rank: 0.5764705882352941, - }, - { - x: 'Other Fuel Combustion', - y: 'Unallocated Fuel Combustion', - v: 1.8, - perc: 0.0685483870967742, - rank: 0.5647058823529412, - }, -]; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-histogram/data.ts b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-histogram/data.ts deleted file mode 100644 index 21ac39d508e0b..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-histogram/data.ts +++ /dev/null @@ -1,32 +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 sort-keys, no-magic-numbers */ -export default [ - { - key: 'Entrance exam', - values: [ - 0.87, 0.944, 1.0, 0.879, 0.69, 0.667, 0.794, 0.838, 0.875, 0.385, 0.968, - 0.804, 1.0, 0.943, 0.96, 0.333, 0.5, 0.929, 0.863, 0.75, 0.957, 0.914, - 1.0, 0.909, 0.742, 0.964, 0.25, 0.75, 0.5, 0.867, 0.909, 0.333, 0.867, - 0.952, 0.857, 0.949, 0.857, 0.333, 0.8, 0.707, 0.833, 0.75, 0.88, 0.771, - 1.0, 1.0, 0.769, 1.0, 0.769, 0.622, 0.909, 0.725, 0.951, 1.0, - ], - }, -]; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-sankey-loop/SankeyLoop.stories.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-sankey-loop/SankeyLoop.stories.tsx deleted file mode 100644 index 97ec3f05e31b2..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-sankey-loop/SankeyLoop.stories.tsx +++ /dev/null @@ -1,41 +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-magic-numbers */ -import { SuperChart } from '@superset-ui/core'; -import SankeyLoopChartPlugin from '@superset-ui/legacy-plugin-chart-sankey-loop'; -import data from './data'; - -new SankeyLoopChartPlugin().configure({ key: 'sankey-loop' }).register(); - -export default { - title: 'Legacy Chart Plugins/legacy-plugin-chart-sankey-loop', -}; - -export const basic = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-sankey-loop/data.ts b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-sankey-loop/data.ts deleted file mode 100644 index b310af21be9b8..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-sankey-loop/data.ts +++ /dev/null @@ -1,52 +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 sort-keys, no-magic-numbers */ -export default [ - { - source: 'Lisdoonvarna', - target: 'Cliffs of Moher', - value: 50, - }, - { - source: 'Cliffs of Moher', - target: 'Lisdoonvarna', - value: 35, - }, - { - source: 'Cliffs of Moher', - target: 'Killarney', - value: 25, - }, - { - source: 'Lisdoonvarna', - target: 'Killarney', - value: 25, - }, - { - source: 'Lisdoonvarna', - target: 'Kinvarra', - value: 25, - }, - { - source: 'Kinvarra', - target: 'Lisdoonvarna', - value: 25, - }, -]; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-sankey/Sankey.stories.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-sankey/Sankey.stories.tsx deleted file mode 100644 index dcc9f5c448f51..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-sankey/Sankey.stories.tsx +++ /dev/null @@ -1,58 +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-magic-numbers */ -import { SuperChart, VizType } from '@superset-ui/core'; -import SankeyChartPlugin from '@superset-ui/legacy-plugin-chart-sankey'; -import ResizableChartDemo from '../../../shared/components/ResizableChartDemo'; -import data from './data'; - -new SankeyChartPlugin().configure({ key: VizType.LegacySankey }).register(); - -export default { - title: 'Legacy Chart Plugins/legacy-plugin-chart-sankey', -}; - -export const basic = () => ( - -); - -export const resizable = () => ( - - {({ width, height }) => ( - - )} - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-sankey/data.ts b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-sankey/data.ts deleted file mode 100644 index df04dff32debd..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-sankey/data.ts +++ /dev/null @@ -1,72 +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 sort-keys, no-magic-numbers */ -export default [ - { - source: 'Energy', - target: 'Electricity and heat', - value: 24.9, - }, - { - source: 'Energy', - target: 'Industry', - value: 14.7, - }, - { - source: 'Energy', - target: 'Transportation', - value: 14.3, - }, - { - source: 'Deforestation', - target: 'Carbon Dioxide', - value: 10.9, - }, - { - source: 'Land Use Change', - target: 'Deforestation', - value: 10.9, - }, - { - source: 'Road', - target: 'Carbon Dioxide', - value: 10.5, - }, - { - source: 'Transportation', - target: 'Road', - value: 10.5, - }, - { - source: 'Residential Buildings', - target: 'Carbon Dioxide', - value: 10.2, - }, - { - source: 'Energy', - target: 'Other Fuel Combustion', - value: 8.6, - }, - { - source: 'Other Industry', - target: 'Carbon Dioxide', - value: 6.6, - }, -]; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/NVD3Area.stories.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/NVD3Area.stories.tsx deleted file mode 100644 index b1f4a9ebeb660..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/NVD3Area.stories.tsx +++ /dev/null @@ -1,35 +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 { VizType } from '@superset-ui/core'; -import { AreaChartPlugin } from '@superset-ui/legacy-preset-chart-nvd3'; - -new AreaChartPlugin().configure({ key: VizType.LegacyArea }).register(); - -export default { - title: 'Legacy Chart Plugins/legacy-preset-chart-nvd3/Area', -}; - -export { stacked } from './stories/stacked'; -export { - stackedWithYAxisBounds, - stackedWithYAxisBoundsMinOnly, -} from './stories/stackedWithBounds'; -export { expanded } from './stories/expanded'; -export { controlsShown } from './stories/controlsShown'; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/data.ts b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/data.ts deleted file mode 100644 index 9372f798b3898..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/data.ts +++ /dev/null @@ -1,1147 +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 sort-keys */ -export default [ - { - key: ['East Asia & Pacific'], - values: [ - { - x: -315619200000.0, - y: 1031863394.0, - }, - { - x: -283996800000.0, - y: 1034767718.0, - }, - { - x: -252460800000.0, - y: 1048537618.0, - }, - { - x: -220924800000.0, - y: 1073600747.0, - }, - { - x: -189388800000.0, - y: 1098305025.0, - }, - { - x: -157766400000.0, - y: 1124077872.0, - }, - { - x: -126230400000.0, - y: 1153296196.0, - }, - { - x: -94694400000.0, - y: 1181582226.0, - }, - { - x: -63158400000.0, - y: 1210302481.0, - }, - { - x: -31536000000.0, - y: 1242569208.0, - }, - { - x: 0.0, - y: 1274888198.0, - }, - { - x: 31536000000.0, - y: 1308215425.0, - }, - { - x: 63072000000.0, - y: 1339781984.0, - }, - { - x: 94694400000.0, - y: 1369814002.0, - }, - { - x: 126230400000.0, - y: 1399636343.0, - }, - { - x: 157766400000.0, - y: 1426562368.0, - }, - { - x: 189302400000.0, - y: 1450503110.0, - }, - { - x: 220924800000.0, - y: 1473091499.0, - }, - { - x: 252460800000.0, - y: 1495573053.0, - }, - { - x: 283996800000.0, - y: 1518439245.0, - }, - { - x: 315532800000.0, - y: 1540823440.0, - }, - { - x: 347155200000.0, - y: 1563926423.0, - }, - { - x: 378691200000.0, - y: 1589245249.0, - }, - { - x: 410227200000.0, - y: 1614538807.0, - }, - { - x: 441763200000.0, - y: 1638618355.0, - }, - { - x: 473385600000.0, - y: 1663385538.0, - }, - { - x: 504921600000.0, - y: 1689894237.0, - }, - { - x: 536457600000.0, - y: 1717904169.0, - }, - { - x: 567993600000.0, - y: 1746140324.0, - }, - { - x: 599616000000.0, - y: 1773743982.0, - }, - { - x: 631152000000.0, - y: 1800365903.0, - }, - { - x: 662688000000.0, - y: 1825955698.0, - }, - { - x: 694224000000.0, - y: 1849800435.0, - }, - { - x: 725846400000.0, - y: 1872809684.0, - }, - { - x: 757382400000.0, - y: 1895892806.0, - }, - { - x: 788918400000.0, - y: 1918680993.0, - }, - { - x: 820454400000.0, - y: 1941199188.0, - }, - { - x: 852076800000.0, - y: 1963227851.0, - }, - { - x: 883612800000.0, - y: 1984508261.0, - }, - { - x: 915148800000.0, - y: 2004495631.0, - }, - { - x: 946684800000.0, - y: 2023534787.0, - }, - { - x: 978307200000.0, - y: 2041915058.0, - }, - { - x: 1009843200000.0, - y: 2059375225.0, - }, - { - x: 1041379200000.0, - y: 2076029083.0, - }, - { - x: 1072915200000.0, - y: 2092086887.0, - }, - { - x: 1104537600000.0, - y: 2107802765.0, - }, - { - x: 1136073600000.0, - y: 2123180222.0, - }, - { - x: 1167609600000.0, - y: 2137834848.0, - }, - { - x: 1199145600000.0, - y: 2152663168.0, - }, - { - x: 1230768000000.0, - y: 2167098541.0, - }, - { - x: 1262304000000.0, - y: 2181465325.0, - }, - { - x: 1293840000000.0, - y: 2195899073.0, - }, - { - x: 1325376000000.0, - y: 2210627396.0, - }, - { - x: 1356998400000.0, - y: 2225523116.0, - }, - { - x: 1388534400000.0, - y: 2240687901.0, - }, - ], - }, - { - key: ['South Asia'], - values: [ - { - x: -315619200000.0, - y: 572036107.0, - }, - { - x: -283996800000.0, - y: 584143236.0, - }, - { - x: -252460800000.0, - y: 596701125.0, - }, - { - x: -220924800000.0, - y: 609571502.0, - }, - { - x: -189388800000.0, - y: 623073110.0, - }, - { - x: -157766400000.0, - y: 636963781.0, - }, - { - x: -126230400000.0, - y: 651325994.0, - }, - { - x: -94694400000.0, - y: 666134328.0, - }, - { - x: -63158400000.0, - y: 681405837.0, - }, - { - x: -31536000000.0, - y: 697060567.0, - }, - { - x: 0.0, - y: 713115397.0, - }, - { - x: 31536000000.0, - y: 729469562.0, - }, - { - x: 63072000000.0, - y: 746222138.0, - }, - { - x: 94694400000.0, - y: 763491289.0, - }, - { - x: 126230400000.0, - y: 781254784.0, - }, - { - x: 157766400000.0, - y: 799620311.0, - }, - { - x: 189302400000.0, - y: 818590962.0, - }, - { - x: 220924800000.0, - y: 838141522.0, - }, - { - x: 252460800000.0, - y: 858266916.0, - }, - { - x: 283996800000.0, - y: 878939779.0, - }, - { - x: 315532800000.0, - y: 900085240.0, - }, - { - x: 347155200000.0, - y: 921521635.0, - }, - { - x: 378691200000.0, - y: 943662470.0, - }, - { - x: 410227200000.0, - y: 966101576.0, - }, - { - x: 441763200000.0, - y: 988898603.0, - }, - { - x: 473385600000.0, - y: 1012110768.0, - }, - { - x: 504921600000.0, - y: 1035703508.0, - }, - { - x: 536457600000.0, - y: 1059570231.0, - }, - { - x: 567993600000.0, - y: 1083711645.0, - }, - { - x: 599616000000.0, - y: 1108132042.0, - }, - { - x: 631152000000.0, - y: 1132775493.0, - }, - { - x: 662688000000.0, - y: 1157716007.0, - }, - { - x: 694224000000.0, - y: 1182766320.0, - }, - { - x: 725846400000.0, - y: 1208041749.0, - }, - { - x: 757382400000.0, - y: 1233462879.0, - }, - { - x: 788918400000.0, - y: 1258964893.0, - }, - { - x: 820454400000.0, - y: 1284467586.0, - }, - { - x: 852076800000.0, - y: 1310020410.0, - }, - { - x: 883612800000.0, - y: 1335551944.0, - }, - { - x: 915148800000.0, - y: 1361114276.0, - }, - { - x: 946684800000.0, - y: 1386400954.0, - }, - { - x: 978307200000.0, - y: 1411281514.0, - }, - { - x: 1009843200000.0, - y: 1436503016.0, - }, - { - x: 1041379200000.0, - y: 1461682019.0, - }, - { - x: 1072915200000.0, - y: 1486598228.0, - }, - { - x: 1104537600000.0, - y: 1511119581.0, - }, - { - x: 1136073600000.0, - y: 1535264779.0, - }, - { - x: 1167609600000.0, - y: 1559023139.0, - }, - { - x: 1199145600000.0, - y: 1582535507.0, - }, - { - x: 1230768000000.0, - y: 1605893501.0, - }, - { - x: 1262304000000.0, - y: 1629189137.0, - }, - { - x: 1293840000000.0, - y: 1652449539.0, - }, - { - x: 1325376000000.0, - y: 1674883124.0, - }, - { - x: 1356998400000.0, - y: 1697955143.0, - }, - { - x: 1388534400000.0, - y: 1720976995.0, - }, - ], - }, - { - key: ['Europe & Central Asia'], - values: [ - { - x: -315619200000.0, - y: 660881033.0, - }, - { - x: -283996800000.0, - y: 668526708.0, - }, - { - x: -252460800000.0, - y: 676418331.0, - }, - { - x: -220924800000.0, - y: 684369825.0, - }, - { - x: -189388800000.0, - y: 692233988.0, - }, - { - x: -157766400000.0, - y: 699849949.0, - }, - { - x: -126230400000.0, - y: 706459925.0, - }, - { - x: -94694400000.0, - y: 712871897.0, - }, - { - x: -63158400000.0, - y: 719034272.0, - }, - { - x: -31536000000.0, - y: 725099571.0, - }, - { - x: 0.0, - y: 730528170.0, - }, - { - x: 31536000000.0, - y: 736135494.0, - }, - { - x: 63072000000.0, - y: 742450677.0, - }, - { - x: 94694400000.0, - y: 748475832.0, - }, - { - x: 126230400000.0, - y: 754297089.0, - }, - { - x: 157766400000.0, - y: 759928297.0, - }, - { - x: 189302400000.0, - y: 765417984.0, - }, - { - x: 220924800000.0, - y: 770663924.0, - }, - { - x: 252460800000.0, - y: 775939424.0, - }, - { - x: 283996800000.0, - y: 781099684.0, - }, - { - x: 315532800000.0, - y: 786407417.0, - }, - { - x: 347155200000.0, - y: 791686169.0, - }, - { - x: 378691200000.0, - y: 796410256.0, - }, - { - x: 410227200000.0, - y: 800966617.0, - }, - { - x: 441763200000.0, - y: 805742116.0, - }, - { - x: 473385600000.0, - y: 810633713.0, - }, - { - x: 504921600000.0, - y: 815649358.0, - }, - { - x: 536457600000.0, - y: 820716895.0, - }, - { - x: 567993600000.0, - y: 825834599.0, - }, - { - x: 599616000000.0, - y: 830998751.0, - }, - { - x: 631152000000.0, - y: 842907397.0, - }, - { - x: 662688000000.0, - y: 846199873.0, - }, - { - x: 694224000000.0, - y: 849633122.0, - }, - { - x: 725846400000.0, - y: 852664422.0, - }, - { - x: 757382400000.0, - y: 854549337.0, - }, - { - x: 788918400000.0, - y: 856102195.0, - }, - { - x: 820454400000.0, - y: 857333424.0, - }, - { - x: 852076800000.0, - y: 858726155.0, - }, - { - x: 883612800000.0, - y: 859806214.0, - }, - { - x: 915148800000.0, - y: 860985079.0, - }, - { - x: 946684800000.0, - y: 862073997.0, - }, - { - x: 978307200000.0, - y: 863554388.0, - }, - { - x: 1009843200000.0, - y: 865246750.0, - }, - { - x: 1041379200000.0, - y: 867562844.0, - }, - { - x: 1072915200000.0, - y: 870213016.0, - }, - { - x: 1104537600000.0, - y: 872968147.0, - }, - { - x: 1136073600000.0, - y: 875755753.0, - }, - { - x: 1167609600000.0, - y: 878819656.0, - }, - { - x: 1199145600000.0, - y: 882358214.0, - }, - { - x: 1230768000000.0, - y: 886063249.0, - }, - { - x: 1262304000000.0, - y: 889630390.0, - }, - { - x: 1293840000000.0, - y: 893094109.0, - }, - { - x: 1325376000000.0, - y: 894962840.0, - }, - { - x: 1356998400000.0, - y: 898837065.0, - }, - { - x: 1388534400000.0, - y: 903095786.0, - }, - ], - }, - { - key: ['Sub-Saharan Africa'], - values: [ - { - x: -315619200000.0, - y: 228268752.0, - }, - { - x: -283996800000.0, - y: 233759990.0, - }, - { - x: -252460800000.0, - y: 239403621.0, - }, - { - x: -220924800000.0, - y: 245217050.0, - }, - { - x: -189388800000.0, - y: 251215851.0, - }, - { - x: -157766400000.0, - y: 257414930.0, - }, - { - x: -126230400000.0, - y: 263830697.0, - }, - { - x: -94694400000.0, - y: 270477558.0, - }, - { - x: -63158400000.0, - y: 277365472.0, - }, - { - x: -31536000000.0, - y: 284502453.0, - }, - { - x: 0.0, - y: 291897883.0, - }, - { - x: 31536000000.0, - y: 299578724.0, - }, - { - x: 63072000000.0, - y: 307524082.0, - }, - { - x: 94694400000.0, - y: 315758889.0, - }, - { - x: 126230400000.0, - y: 324316627.0, - }, - { - x: 157766400000.0, - y: 333222446.0, - }, - { - x: 189302400000.0, - y: 342489556.0, - }, - { - x: 220924800000.0, - y: 352109622.0, - }, - { - x: 252460800000.0, - y: 362076216.0, - }, - { - x: 283996800000.0, - y: 372390972.0, - }, - { - x: 315532800000.0, - y: 383043891.0, - }, - { - x: 347155200000.0, - y: 394021126.0, - }, - { - x: 378691200000.0, - y: 405328909.0, - }, - { - x: 410227200000.0, - y: 416982682.0, - }, - { - x: 441763200000.0, - y: 429008541.0, - }, - { - x: 473385600000.0, - y: 441414277.0, - }, - { - x: 504921600000.0, - y: 454197298.0, - }, - { - x: 536457600000.0, - y: 467337821.0, - }, - { - x: 567993600000.0, - y: 480809661.0, - }, - { - x: 599616000000.0, - y: 494580339.0, - }, - { - x: 631152000000.0, - y: 508616039.0, - }, - { - x: 662688000000.0, - y: 523007873.0, - }, - { - x: 694224000000.0, - y: 537759561.0, - }, - { - x: 725846400000.0, - y: 552842678.0, - }, - { - x: 757382400000.0, - y: 568228356.0, - }, - { - x: 788918400000.0, - y: 583892679.0, - }, - { - x: 820454400000.0, - y: 599858645.0, - }, - { - x: 852076800000.0, - y: 616161312.0, - }, - { - x: 883612800000.0, - y: 632857149.0, - }, - { - x: 915148800000.0, - y: 650030484.0, - }, - { - x: 946684800000.0, - y: 667742098.0, - }, - { - x: 978307200000.0, - y: 685795280.0, - }, - { - x: 1009843200000.0, - y: 704102354.0, - }, - { - x: 1041379200000.0, - y: 722925207.0, - }, - { - x: 1072915200000.0, - y: 742396040.0, - }, - { - x: 1104537600000.0, - y: 762555740.0, - }, - { - x: 1136073600000.0, - y: 783427658.0, - }, - { - x: 1167609600000.0, - y: 805010175.0, - }, - { - x: 1199145600000.0, - y: 827287676.0, - }, - { - x: 1230768000000.0, - y: 850225069.0, - }, - { - x: 1262304000000.0, - y: 873800152.0, - }, - { - x: 1293840000000.0, - y: 898002051.0, - }, - { - x: 1325376000000.0, - y: 922840423.0, - }, - { - x: 1356998400000.0, - y: 948287652.0, - }, - { - x: 1388534400000.0, - y: 974315323.0, - }, - ], - }, - { - key: ['Latin America & Caribbean'], - values: [ - { - x: -315619200000.0, - y: 220564224.0, - }, - { - x: -283996800000.0, - y: 226764342.0, - }, - { - x: -252460800000.0, - y: 233183206.0, - }, - { - x: -220924800000.0, - y: 239771182.0, - }, - { - x: -189388800000.0, - y: 246458356.0, - }, - { - x: -157766400000.0, - y: 253195267.0, - }, - { - x: -126230400000.0, - y: 259965218.0, - }, - { - x: -94694400000.0, - y: 266776414.0, - }, - { - x: -63158400000.0, - y: 273654630.0, - }, - { - x: -31536000000.0, - y: 280641049.0, - }, - { - x: 0.0, - y: 287763515.0, - }, - { - x: 31536000000.0, - y: 295026304.0, - }, - { - x: 63072000000.0, - y: 302408883.0, - }, - { - x: 94694400000.0, - y: 309902169.0, - }, - { - x: 126230400000.0, - y: 317479496.0, - }, - { - x: 157766400000.0, - y: 325120067.0, - }, - { - x: 189302400000.0, - y: 332817916.0, - }, - { - x: 220924800000.0, - y: 340569396.0, - }, - { - x: 252460800000.0, - y: 348391181.0, - }, - { - x: 283996800000.0, - y: 356288443.0, - }, - { - x: 315532800000.0, - y: 364270961.0, - }, - { - x: 347155200000.0, - y: 372330102.0, - }, - { - x: 378691200000.0, - y: 380466998.0, - }, - { - x: 410227200000.0, - y: 388654061.0, - }, - { - x: 441763200000.0, - y: 396869481.0, - }, - { - x: 473385600000.0, - y: 405083115.0, - }, - { - x: 504921600000.0, - y: 413292690.0, - }, - { - x: 536457600000.0, - y: 421490233.0, - }, - { - x: 567993600000.0, - y: 429668211.0, - }, - { - x: 599616000000.0, - y: 437843614.0, - }, - { - x: 631152000000.0, - y: 445998222.0, - }, - { - x: 662688000000.0, - y: 454117634.0, - }, - { - x: 694224000000.0, - y: 462201058.0, - }, - { - x: 725846400000.0, - y: 470263697.0, - }, - { - x: 757382400000.0, - y: 478310786.0, - }, - { - x: 788918400000.0, - y: 486343677.0, - }, - { - x: 820454400000.0, - y: 494384205.0, - }, - { - x: 852076800000.0, - y: 502390020.0, - }, - { - x: 883612800000.0, - y: 510356845.0, - }, - { - x: 915148800000.0, - y: 518188225.0, - }, - { - x: 946684800000.0, - y: 525886558.0, - }, - { - x: 978307200000.0, - y: 533449671.0, - }, - { - x: 1009843200000.0, - y: 540884684.0, - }, - { - x: 1041379200000.0, - y: 548225528.0, - }, - { - x: 1072915200000.0, - y: 555515431.0, - }, - { - x: 1104537600000.0, - y: 562783235.0, - }, - { - x: 1136073600000.0, - y: 570029991.0, - }, - { - x: 1167609600000.0, - y: 577248307.0, - }, - { - x: 1199145600000.0, - y: 584435842.0, - }, - { - x: 1230768000000.0, - y: 591577623.0, - }, - { - x: 1262304000000.0, - y: 598662941.0, - }, - { - x: 1293840000000.0, - y: 605674766.0, - }, - { - x: 1325376000000.0, - y: 612617659.0, - }, - { - x: 1356998400000.0, - y: 619487273.0, - }, - { - x: 1388534400000.0, - y: 626270167.0, - }, - ], - }, -]; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/stories/controlsShown.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/stories/controlsShown.tsx deleted file mode 100644 index 659b1636a6d2c..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/stories/controlsShown.tsx +++ /dev/null @@ -1,53 +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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; -import data from '../data'; - -export const controlsShown = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/stories/expanded.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/stories/expanded.tsx deleted file mode 100644 index 980d4360eb200..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/stories/expanded.tsx +++ /dev/null @@ -1,53 +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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; -import data from '../data'; - -export const expanded = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/stories/stacked.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/stories/stacked.tsx deleted file mode 100644 index a161ed68fab0c..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/stories/stacked.tsx +++ /dev/null @@ -1,54 +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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; -import data from '../data'; - -export const stacked = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/stories/stackedWithBounds.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/stories/stackedWithBounds.tsx deleted file mode 100644 index a7104b60aa6b4..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Area/stories/stackedWithBounds.tsx +++ /dev/null @@ -1,88 +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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; -import data from '../data'; - -export const stackedWithYAxisBounds = () => ( - -); - -stackedWithYAxisBounds.storyName = 'Stacked with yAxisBounds'; - -export const stackedWithYAxisBoundsMinOnly = () => ( - -); - -stackedWithYAxisBoundsMinOnly.storyName = 'Stacked with yAxisBounds min only'; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/NVD3Bar.stories.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/NVD3Bar.stories.tsx deleted file mode 100644 index 5da1177c33942..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/NVD3Bar.stories.tsx +++ /dev/null @@ -1,32 +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 { VizType } from '@superset-ui/core'; -import { BarChartPlugin } from '@superset-ui/legacy-preset-chart-nvd3'; - -new BarChartPlugin().configure({ key: VizType.LegacyBar }).register(); - -export default { - title: 'Legacy Chart Plugins/legacy-preset-chart-nvd3/Bar', -}; - -export { basic } from './stories/basic'; -export { barWithValues } from './stories/barWithValues'; -export { barWithPositiveAndNegativeValues } from './stories/barWithPositiveAndNegativeValues'; -export { stackedBarWithValues } from './stories/stackedBarWithValues'; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/data.ts b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/data.ts deleted file mode 100644 index ef8338b65949c..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/data.ts +++ /dev/null @@ -1,247 +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 sort-keys */ -export default [ - { - key: ['East Asia & Pacific'], - values: [ - { - x: -315619200000.0, - y: 1031863394.0, - }, - { - x: -283996800000.0, - y: 1034767718.0, - }, - { - x: -252460800000.0, - y: 1048537618.0, - }, - { - x: -220924800000.0, - y: 1073600747.0, - }, - { - x: -189388800000.0, - y: 1098305025.0, - }, - { - x: -157766400000.0, - y: 1124077872.0, - }, - { - x: -126230400000.0, - y: 1153296196.0, - }, - { - x: -94694400000.0, - y: 1181582226.0, - }, - { - x: -63158400000.0, - y: 1210302481.0, - }, - { - x: -31536000000.0, - y: 1242569208.0, - }, - ], - }, - { - key: ['South Asia'], - values: [ - { - x: -315619200000.0, - y: 572036107.0, - }, - { - x: -283996800000.0, - y: 584143236.0, - }, - { - x: -252460800000.0, - y: 596701125.0, - }, - { - x: -220924800000.0, - y: 609571502.0, - }, - { - x: -189388800000.0, - y: 623073110.0, - }, - { - x: -157766400000.0, - y: 636963781.0, - }, - { - x: -126230400000.0, - y: 651325994.0, - }, - { - x: -94694400000.0, - y: 666134328.0, - }, - { - x: -63158400000.0, - y: 681405837.0, - }, - { - x: -31536000000.0, - y: 697060567.0, - }, - ], - }, - { - key: ['Europe & Central Asia'], - values: [ - { - x: -315619200000.0, - y: 660881033.0, - }, - { - x: -283996800000.0, - y: 668526708.0, - }, - { - x: -252460800000.0, - y: 676418331.0, - }, - { - x: -220924800000.0, - y: 684369825.0, - }, - { - x: -189388800000.0, - y: 692233988.0, - }, - { - x: -157766400000.0, - y: 699849949.0, - }, - { - x: -126230400000.0, - y: 706459925.0, - }, - { - x: -94694400000.0, - y: 712871897.0, - }, - { - x: -63158400000.0, - y: 719034272.0, - }, - { - x: -31536000000.0, - y: 725099571.0, - }, - ], - }, - { - key: ['Sub-Saharan Africa'], - values: [ - { - x: -315619200000.0, - y: 228268752.0, - }, - { - x: -283996800000.0, - y: 233759990.0, - }, - { - x: -252460800000.0, - y: 239403621.0, - }, - { - x: -220924800000.0, - y: 245217050.0, - }, - { - x: -189388800000.0, - y: 251215851.0, - }, - { - x: -157766400000.0, - y: 257414930.0, - }, - { - x: -126230400000.0, - y: 263830697.0, - }, - { - x: -94694400000.0, - y: 270477558.0, - }, - { - x: -63158400000.0, - y: 277365472.0, - }, - { - x: -31536000000.0, - y: 284502453.0, - }, - ], - }, - { - key: ['Latin America & Caribbean'], - values: [ - { - x: -315619200000.0, - y: 220564224.0, - }, - { - x: -283996800000.0, - y: 226764342.0, - }, - { - x: -252460800000.0, - y: 233183206.0, - }, - { - x: -220924800000.0, - y: 239771182.0, - }, - { - x: -189388800000.0, - y: 246458356.0, - }, - { - x: -157766400000.0, - y: 253195267.0, - }, - { - x: -126230400000.0, - y: 259965218.0, - }, - { - x: -94694400000.0, - y: 266776414.0, - }, - { - x: -63158400000.0, - y: 273654630.0, - }, - { - x: -31536000000.0, - y: 280641049.0, - }, - ], - }, -]; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/stories/barWithPositiveAndNegativeValues.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/stories/barWithPositiveAndNegativeValues.tsx deleted file mode 100644 index 449a54f6c3f04..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/stories/barWithPositiveAndNegativeValues.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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; -import data from '../data'; - -export const barWithPositiveAndNegativeValues = () => ( - ({ - ...group, - values: group.values.map(pair => ({ - ...pair, - y: (i % 2 === 0 ? 1 : -1) * pair.y, - })), - })), - }, - ]} - formData={{ - bottomMargin: 'auto', - colorScheme: 'd3Category10', - contribution: false, - groupby: ['region'], - lineInterpolation: 'linear', - metrics: ['sum__SP_POP_TOTL'], - richTooltip: true, - showBarValue: true, - showBrush: 'auto', - showControls: false, - showLegend: true, - stackedStyle: 'stack', - vizType: VizType.LegacyBar, - xAxisFormat: '%Y', - xAxisLabel: '', - xAxisShowminmax: false, - xTicksLayout: 'auto', - yAxisBounds: [null, null], - yAxisFormat: '.3s', - yLogScale: false, - }} - /> -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/stories/barWithValues.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/stories/barWithValues.tsx deleted file mode 100644 index 5679050f65f3f..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/stories/barWithValues.tsx +++ /dev/null @@ -1,54 +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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; -import data from '../data'; - -export const barWithValues = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/stories/basic.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/stories/basic.tsx deleted file mode 100644 index 00ad09b51f16b..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/stories/basic.tsx +++ /dev/null @@ -1,54 +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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; -import data from '../data'; - -export const basic = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/stories/stackedBarWithValues.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/stories/stackedBarWithValues.tsx deleted file mode 100644 index fb931e39601fd..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Bar/stories/stackedBarWithValues.tsx +++ /dev/null @@ -1,55 +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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; -import data from '../data'; - -export const stackedBarWithValues = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/DistBar/NVD3DistBar.stories.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/DistBar/NVD3DistBar.stories.tsx deleted file mode 100644 index 5b9ef501299b2..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/DistBar/NVD3DistBar.stories.tsx +++ /dev/null @@ -1,29 +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 { DistBarChartPlugin } from '@superset-ui/legacy-preset-chart-nvd3'; - -new DistBarChartPlugin().configure({ key: 'dist-bar' }).register(); - -export default { - title: 'Legacy Chart Plugins/legacy-preset-chart-nvd3/DistBar', -}; - -export { basic } from './stories/basic'; -export { manyBars } from './stories/manyBars'; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/DistBar/data.ts b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/DistBar/data.ts deleted file mode 100644 index a5cc5b534d373..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/DistBar/data.ts +++ /dev/null @@ -1,112 +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 sort-keys, no-magic-numbers */ -export default [ - { - key: 'sum__sum_boys', - values: [ - { - x: 'CA', - y: 5430796, - }, - { - x: 'TX', - y: 3311985, - }, - { - x: 'NY', - y: 3543961, - }, - { - x: 'OH', - y: 2376385, - }, - { - x: 'PA', - y: 2390275, - }, - { - x: 'IL', - y: 2357411, - }, - { - x: 'MI', - y: 1938321, - }, - { - x: 'FL', - y: 1968060, - }, - { - x: 'NJ', - y: 1486126, - }, - { - x: 'MA', - y: 1285126, - }, - ], - }, - { - key: 'sum__sum_girls', - values: [ - { - x: 'CA', - y: 3567754, - }, - { - x: 'TX', - y: 2313186, - }, - { - x: 'NY', - y: 2280733, - }, - { - x: 'OH', - y: 1622814, - }, - { - x: 'PA', - y: 1615383, - }, - { - x: 'IL', - y: 1614427, - }, - { - x: 'MI', - y: 1326229, - }, - { - x: 'FL', - y: 1312593, - }, - { - x: 'NJ', - y: 992702, - }, - { - x: 'MA', - y: 842146, - }, - ], - }, -]; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/DistBar/stories/basic.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/DistBar/stories/basic.tsx deleted file mode 100644 index c262c2cc2b29a..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/DistBar/stories/basic.tsx +++ /dev/null @@ -1,48 +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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; -import data from '../data'; - -export const basic = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/DistBar/stories/manyBars.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/DistBar/stories/manyBars.tsx deleted file mode 100644 index 246b590c02b9e..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/DistBar/stories/manyBars.tsx +++ /dev/null @@ -1,58 +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 { SuperChart, VizType, seedRandom } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; - -const data: { - key: string; - values: { - x: string; - y: number; - }[]; -}[] = [{ key: 'sth', values: [] }]; -const LONG_LABEL = - 'some extremely ridiculously extremely extremely extremely ridiculously extremely extremely ridiculously extremely extremely ridiculously extremely long category'; - -for (let i = 0; i < 50; i += 1) { - data[0].values.push({ - x: `${LONG_LABEL.substring( - 0, - Math.round(seedRandom() * LONG_LABEL.length), - )} ${i + 1}`, - y: Math.round(seedRandom() * 10000), - }); -} - -export const manyBars = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/NVD3Line.stories.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/NVD3Line.stories.tsx deleted file mode 100644 index 0ba739c1f3a4a..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/NVD3Line.stories.tsx +++ /dev/null @@ -1,32 +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 { VizType } from '@superset-ui/core'; -import { LineChartPlugin } from '@superset-ui/legacy-preset-chart-nvd3'; - -new LineChartPlugin().configure({ key: VizType.LegacyLine }).register(); - -export default { - title: 'Legacy Chart Plugins/legacy-preset-chart-nvd3/Line', -}; - -export { basic } from './stories/basic'; -export { markers } from './stories/markers'; -export { logScale } from './stories/logScale'; -export { yAxisBounds } from './stories/yAxisBounds'; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/data.ts b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/data.ts deleted file mode 100644 index 5d43d8e4bec22..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/data.ts +++ /dev/null @@ -1,927 +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 sort-keys, no-magic-numbers */ -export default [ - { - key: ['Christopher'], - values: [ - { - x: -157766400000.0, - y: 24703, - }, - { - x: -126230400000.0, - y: 27861, - }, - { - x: -94694400000.0, - y: 29436, - }, - { - x: -63158400000.0, - y: 31463, - }, - { - x: -31536000000.0, - y: 35718, - }, - { - x: 0.0, - y: 41758, - }, - { - x: 31536000000.0, - y: 48172, - }, - { - x: 63072000000.0, - y: 52092, - }, - { - x: 94694400000.0, - y: 48217, - }, - { - x: 126230400000.0, - y: 48476, - }, - { - x: 157766400000.0, - y: 46438, - }, - { - x: 189302400000.0, - y: 45086, - }, - { - x: 220924800000.0, - y: 46610, - }, - { - x: 252460800000.0, - y: 47107, - }, - { - x: 283996800000.0, - y: 50514, - }, - { - x: 315532800000.0, - y: 48969, - }, - { - x: 347155200000.0, - y: 50108, - }, - { - x: 378691200000.0, - y: 59055, - }, - { - x: 410227200000.0, - y: 59188, - }, - { - x: 441763200000.0, - y: 59859, - }, - { - x: 473385600000.0, - y: 59516, - }, - { - x: 504921600000.0, - y: 56633, - }, - { - x: 536457600000.0, - y: 54466, - }, - { - x: 567993600000.0, - y: 52996, - }, - { - x: 599616000000.0, - y: 53205, - }, - { - x: 631152000000.0, - y: 52322, - }, - { - x: 662688000000.0, - y: 47109, - }, - { - x: 694224000000.0, - y: 42470, - }, - { - x: 725846400000.0, - y: 38257, - }, - { - x: 757382400000.0, - y: 34823, - }, - { - x: 788918400000.0, - y: 32728, - }, - { - x: 820454400000.0, - y: 30988, - }, - { - x: 852076800000.0, - y: 29179, - }, - { - x: 883612800000.0, - y: 27083, - }, - { - x: 915148800000.0, - y: 25700, - }, - { - x: 946684800000.0, - y: 24959, - }, - { - x: 978307200000.0, - y: 23180, - }, - { - x: 1009843200000.0, - y: 21731, - }, - { - x: 1041379200000.0, - y: 20793, - }, - { - x: 1072915200000.0, - y: 19739, - }, - { - x: 1104537600000.0, - y: 19190, - }, - { - x: 1136073600000.0, - y: 19674, - }, - { - x: 1167609600000.0, - y: 19986, - }, - { - x: 1199145600000.0, - y: 17771, - }, - ], - }, - { - key: ['David'], - values: [ - { - x: -157766400000.0, - y: 67646, - }, - { - x: -126230400000.0, - y: 66207, - }, - { - x: -94694400000.0, - y: 66581, - }, - { - x: -63158400000.0, - y: 63531, - }, - { - x: -31536000000.0, - y: 63502, - }, - { - x: 0.0, - y: 61570, - }, - { - x: 31536000000.0, - y: 52948, - }, - { - x: 63072000000.0, - y: 46218, - }, - { - x: 94694400000.0, - y: 40968, - }, - { - x: 126230400000.0, - y: 41654, - }, - { - x: 157766400000.0, - y: 39019, - }, - { - x: 189302400000.0, - y: 39165, - }, - { - x: 220924800000.0, - y: 40407, - }, - { - x: 252460800000.0, - y: 40533, - }, - { - x: 283996800000.0, - y: 41898, - }, - { - x: 315532800000.0, - y: 41743, - }, - { - x: 347155200000.0, - y: 40486, - }, - { - x: 378691200000.0, - y: 40283, - }, - { - x: 410227200000.0, - y: 39048, - }, - { - x: 441763200000.0, - y: 38346, - }, - { - x: 473385600000.0, - y: 38395, - }, - { - x: 504921600000.0, - y: 37021, - }, - { - x: 536457600000.0, - y: 36672, - }, - { - x: 567993600000.0, - y: 35214, - }, - { - x: 599616000000.0, - y: 35139, - }, - { - x: 631152000000.0, - y: 33661, - }, - { - x: 662688000000.0, - y: 30347, - }, - { - x: 694224000000.0, - y: 28344, - }, - { - x: 725846400000.0, - y: 26947, - }, - { - x: 757382400000.0, - y: 24784, - }, - { - x: 788918400000.0, - y: 22967, - }, - { - x: 820454400000.0, - y: 22941, - }, - { - x: 852076800000.0, - y: 21824, - }, - { - x: 883612800000.0, - y: 20816, - }, - { - x: 915148800000.0, - y: 20267, - }, - { - x: 946684800000.0, - y: 19695, - }, - { - x: 978307200000.0, - y: 19281, - }, - { - x: 1009843200000.0, - y: 18600, - }, - { - x: 1041379200000.0, - y: 18557, - }, - { - x: 1072915200000.0, - y: 18315, - }, - { - x: 1104537600000.0, - y: 18017, - }, - { - x: 1136073600000.0, - y: 17510, - }, - { - x: 1167609600000.0, - y: 17400, - }, - { - x: 1199145600000.0, - y: 16049, - }, - ], - }, - { - key: ['James'], - values: [ - { - x: -157766400000.0, - y: 67506, - }, - { - x: -126230400000.0, - y: 65036, - }, - { - x: -94694400000.0, - y: 61554, - }, - { - x: -63158400000.0, - y: 60584, - }, - { - x: -31536000000.0, - y: 59824, - }, - { - x: 0.0, - y: 61597, - }, - { - x: 31536000000.0, - y: 54463, - }, - { - x: 63072000000.0, - y: 46960, - }, - { - x: 94694400000.0, - y: 42782, - }, - { - x: 126230400000.0, - y: 41258, - }, - { - x: 157766400000.0, - y: 39471, - }, - { - x: 189302400000.0, - y: 38203, - }, - { - x: 220924800000.0, - y: 39916, - }, - { - x: 252460800000.0, - y: 39783, - }, - { - x: 283996800000.0, - y: 39237, - }, - { - x: 315532800000.0, - y: 39185, - }, - { - x: 347155200000.0, - y: 38176, - }, - { - x: 378691200000.0, - y: 38750, - }, - { - x: 410227200000.0, - y: 36228, - }, - { - x: 441763200000.0, - y: 35728, - }, - { - x: 473385600000.0, - y: 35750, - }, - { - x: 504921600000.0, - y: 33955, - }, - { - x: 536457600000.0, - y: 32552, - }, - { - x: 567993600000.0, - y: 32418, - }, - { - x: 599616000000.0, - y: 32658, - }, - { - x: 631152000000.0, - y: 32288, - }, - { - x: 662688000000.0, - y: 30460, - }, - { - x: 694224000000.0, - y: 28450, - }, - { - x: 725846400000.0, - y: 26193, - }, - { - x: 757382400000.0, - y: 24706, - }, - { - x: 788918400000.0, - y: 22691, - }, - { - x: 820454400000.0, - y: 21122, - }, - { - x: 852076800000.0, - y: 20368, - }, - { - x: 883612800000.0, - y: 19651, - }, - { - x: 915148800000.0, - y: 18508, - }, - { - x: 946684800000.0, - y: 17939, - }, - { - x: 978307200000.0, - y: 17023, - }, - { - x: 1009843200000.0, - y: 16905, - }, - { - x: 1041379200000.0, - y: 16832, - }, - { - x: 1072915200000.0, - y: 16459, - }, - { - x: 1104537600000.0, - y: 16046, - }, - { - x: 1136073600000.0, - y: 16139, - }, - { - x: 1167609600000.0, - y: 15821, - }, - { - x: 1199145600000.0, - y: 14920, - }, - ], - }, - { - key: ['John'], - values: [ - { - x: -157766400000.0, - y: 71390, - }, - { - x: -126230400000.0, - y: 64858, - }, - { - x: -94694400000.0, - y: 61480, - }, - { - x: -63158400000.0, - y: 60754, - }, - { - x: -31536000000.0, - y: 58644, - }, - { - x: 0.0, - y: 58348, - }, - { - x: 31536000000.0, - y: 51382, - }, - { - x: 63072000000.0, - y: 43028, - }, - { - x: 94694400000.0, - y: 39061, - }, - { - x: 126230400000.0, - y: 37553, - }, - { - x: 157766400000.0, - y: 34970, - }, - { - x: 189302400000.0, - y: 33876, - }, - { - x: 220924800000.0, - y: 34103, - }, - { - x: 252460800000.0, - y: 33895, - }, - { - x: 283996800000.0, - y: 35305, - }, - { - x: 315532800000.0, - y: 35131, - }, - { - x: 347155200000.0, - y: 34761, - }, - { - x: 378691200000.0, - y: 34560, - }, - { - x: 410227200000.0, - y: 33047, - }, - { - x: 441763200000.0, - y: 32484, - }, - { - x: 473385600000.0, - y: 31397, - }, - { - x: 504921600000.0, - y: 30103, - }, - { - x: 536457600000.0, - y: 29462, - }, - { - x: 567993600000.0, - y: 29301, - }, - { - x: 599616000000.0, - y: 29751, - }, - { - x: 631152000000.0, - y: 29011, - }, - { - x: 662688000000.0, - y: 27727, - }, - { - x: 694224000000.0, - y: 26156, - }, - { - x: 725846400000.0, - y: 24918, - }, - { - x: 757382400000.0, - y: 24119, - }, - { - x: 788918400000.0, - y: 23174, - }, - { - x: 820454400000.0, - y: 22104, - }, - { - x: 852076800000.0, - y: 21330, - }, - { - x: 883612800000.0, - y: 20556, - }, - { - x: 915148800000.0, - y: 20280, - }, - { - x: 946684800000.0, - y: 20032, - }, - { - x: 978307200000.0, - y: 18839, - }, - { - x: 1009843200000.0, - y: 17400, - }, - { - x: 1041379200000.0, - y: 17170, - }, - { - x: 1072915200000.0, - y: 16381, - }, - { - x: 1104537600000.0, - y: 15692, - }, - { - x: 1136073600000.0, - y: 15083, - }, - { - x: 1167609600000.0, - y: 14348, - }, - { - x: 1199145600000.0, - y: 13110, - }, - ], - }, - { - key: ['Michael'], - values: [ - { - x: -157766400000.0, - y: 80812, - }, - { - x: -126230400000.0, - y: 79709, - }, - { - x: -94694400000.0, - y: 82204, - }, - { - x: -63158400000.0, - y: 81785, - }, - { - x: -31536000000.0, - y: 84893, - }, - { - x: 0.0, - y: 85015, - }, - { - x: 31536000000.0, - y: 77321, - }, - { - x: 63072000000.0, - y: 71197, - }, - { - x: 94694400000.0, - y: 67598, - }, - { - x: 126230400000.0, - y: 67304, - }, - { - x: 157766400000.0, - y: 68149, - }, - { - x: 189302400000.0, - y: 66686, - }, - { - x: 220924800000.0, - y: 67344, - }, - { - x: 252460800000.0, - y: 66875, - }, - { - x: 283996800000.0, - y: 67473, - }, - { - x: 315532800000.0, - y: 68375, - }, - { - x: 347155200000.0, - y: 68467, - }, - { - x: 378691200000.0, - y: 67904, - }, - { - x: 410227200000.0, - y: 67708, - }, - { - x: 441763200000.0, - y: 67457, - }, - { - x: 473385600000.0, - y: 64667, - }, - { - x: 504921600000.0, - y: 63959, - }, - { - x: 536457600000.0, - y: 63442, - }, - { - x: 567993600000.0, - y: 63924, - }, - { - x: 599616000000.0, - y: 65233, - }, - { - x: 631152000000.0, - y: 65138, - }, - { - x: 662688000000.0, - y: 60646, - }, - { - x: 694224000000.0, - y: 54216, - }, - { - x: 725846400000.0, - y: 49443, - }, - { - x: 757382400000.0, - y: 44361, - }, - { - x: 788918400000.0, - y: 41311, - }, - { - x: 820454400000.0, - y: 38284, - }, - { - x: 852076800000.0, - y: 37459, - }, - { - x: 883612800000.0, - y: 36525, - }, - { - x: 915148800000.0, - y: 33820, - }, - { - x: 946684800000.0, - y: 31956, - }, - { - x: 978307200000.0, - y: 29612, - }, - { - x: 1009843200000.0, - y: 28156, - }, - { - x: 1041379200000.0, - y: 27031, - }, - { - x: 1072915200000.0, - y: 25418, - }, - { - x: 1104537600000.0, - y: 23678, - }, - { - x: 1136073600000.0, - y: 22498, - }, - { - x: 1167609600000.0, - y: 21805, - }, - { - x: 1199145600000.0, - y: 20271, - }, - ], - }, -]; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/stories/basic.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/stories/basic.tsx deleted file mode 100644 index 3aea91e7f40d8..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/stories/basic.tsx +++ /dev/null @@ -1,52 +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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; -import data from '../data'; - -export const basic = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/stories/logScale.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/stories/logScale.tsx deleted file mode 100644 index f93109f73e53e..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/stories/logScale.tsx +++ /dev/null @@ -1,39 +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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; -import data from '../data'; - -export const logScale = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/stories/markers.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/stories/markers.tsx deleted file mode 100644 index c2a83b8eb457c..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/stories/markers.tsx +++ /dev/null @@ -1,52 +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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; -import data from '../data'; - -export const markers = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/stories/yAxisBounds.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/stories/yAxisBounds.tsx deleted file mode 100644 index c705c0b2b45e7..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Line/stories/yAxisBounds.tsx +++ /dev/null @@ -1,97 +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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; -import data from '../data'; - -export const yAxisBounds = () => ( -
-

yAxisBounds

-
yAxisBounds=undefined
- -
yAxisBounds=[0, 60000]
- -
yAxisBounds=[null, 60000]
- -
yAxisBounds=[40000, null]
- -
yAxisBounds=[40000, null] with Legend
- -
-); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Pie/NVD3Pie.stories.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Pie/NVD3Pie.stories.tsx deleted file mode 100644 index 5fcfa870204c7..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Pie/NVD3Pie.stories.tsx +++ /dev/null @@ -1,30 +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 { VizType } from '@superset-ui/core'; -import { PieChartPlugin } from '@superset-ui/legacy-preset-chart-nvd3'; - -new PieChartPlugin().configure({ key: VizType.Pie }).register(); - -export default { - title: 'Legacy Chart Plugins/legacy-preset-chart-nvd3/Pie', -}; - -export { basic } from './stories/basic'; -export { noData } from './stories/noData'; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Pie/data.ts b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Pie/data.ts deleted file mode 100644 index 6db3c5f69653b..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Pie/data.ts +++ /dev/null @@ -1,30 +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 sort-keys, no-magic-numbers */ -export default [ - { - x: 'boy', - y: 48133355, - }, - { - x: 'girl', - y: 32546308, - }, -]; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Pie/stories/basic.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Pie/stories/basic.tsx deleted file mode 100644 index 0fd34b6ec60f9..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Pie/stories/basic.tsx +++ /dev/null @@ -1,42 +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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; -import data from '../data'; - -export const basic = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Pie/stories/noData.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Pie/stories/noData.tsx deleted file mode 100644 index 028305901a6cf..0000000000000 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-preset-chart-nvd3/Pie/stories/noData.tsx +++ /dev/null @@ -1,41 +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 { SuperChart, VizType } from '@superset-ui/core'; -import dummyDatasource from '../../../../../shared/dummyDatasource'; - -export const noData = () => ( - -); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-chart/ChartDataProvider.stories.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-chart/ChartDataProvider.stories.tsx index f0538196366a8..ca3ef61872712 100644 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-chart/ChartDataProvider.stories.tsx +++ b/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-chart/ChartDataProvider.stories.tsx @@ -23,7 +23,6 @@ import { SupersetClient, } from '@superset-ui/core'; import { BigNumberChartPlugin } from '@superset-ui/plugin-chart-echarts'; -import LegacySankeyPlugin from '@superset-ui/legacy-plugin-chart-sankey'; import { WordCloudChartPlugin } from '@superset-ui/plugin-chart-word-cloud'; import { @@ -44,8 +43,6 @@ const WORD_CLOUD = 'new_word_cloud'; new BigNumberChartPlugin().configure({ key: BIG_NUMBER }).register(); // eslint-disable-next-line -new LegacySankeyPlugin().configure({ key: SANKEY }).register(); -// eslint-disable-next-line new WordCloudChartPlugin().configure({ key: WORD_CLOUD }).register(); const VIS_TYPES = [BIG_NUMBER, SANKEY, SUNBURST, WORD_CLOUD, WORD_CLOUD_LEGACY]; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-style/Theme.stories.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-style/Theme.stories.tsx index 63ede4a8a7c36..b46c9dfb81297 100644 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-style/Theme.stories.tsx +++ b/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-style/Theme.stories.tsx @@ -25,23 +25,96 @@ export default { export const ThemeColors = () => { const { colors } = supersetTheme; - return Object.keys(colors).map(collection => ( + + // Define tones to be displayed in columns + const tones = [ + 'dark2', + 'dark1', + 'base', + 'light1', + 'light2', + 'light3', + 'light4', + 'light5', + ]; + const colorTypes = [ + 'primary', + 'secondary', + 'grayscale', + 'error', + 'warning', + 'alert', + 'success', + 'info', + ]; + return (
-

{collection}

- - {Object.keys(colors[collection]).map(k => { - const hex = colors[collection][k]; - return ( - - - -
- {t('START (INCLUSIVE)')}{' '} - -
- onChange('sinceGrain', value)} - /> - - - )} - - -
- {t('END (EXCLUSIVE)')}{' '} - + onChange('untilMode', value)} - /> - {untilMode === 'specific' && ( - - - onChange('untilDatetime', datetime.format(MOMENT_FORMAT)) - } - allowClear={false} - locale={datePickerLocale} - getPopupContainer={triggerNode => - props.isOverflowingFilterBar - ? (triggerNode.parentNode as HTMLElement) - : document.body - } - /> - - )} - {untilMode === 'relative' && ( - -
- - onGrainValue('untilGrainValue', value || 1) + {sinceMode === 'specific' && ( + + + onChange('sinceDatetime', datetime.format(DAYJS_FORMAT)) + } + allowClear={false} + getPopupContainer={(triggerNode: HTMLElement) => + props.isOverflowingFilterBar + ? (triggerNode.parentNode as HTMLElement) + : document.body } - onStep={value => onGrainValue('untilGrainValue', value || 1)} - /> - - - onChange('sinceGrain', value)} + /> + + + )} + + +
+ {t('END (EXCLUSIVE)')}{' '} + +
+ onChange('untilGrain', value)} + /> + + )} - - - )} - + + + {sinceMode === 'relative' && untilMode === 'relative' && ( +
+
{t('Anchor to')}
+ +
+ + + {t('NOW')} + + + {t('Date/Time')} + + + + {anchorMode !== 'now' && ( + + + onChange('anchorValue', datetime.format(DAYJS_FORMAT)) + } + allowClear={false} + className="control-anchor-to-datetime" + getPopupContainer={(triggerNode: HTMLElement) => + props.isOverflowingFilterBar + ? (triggerNode.parentNode as HTMLElement) + : document.body + } + /> + + )} + + + )} + + ); } diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/tests/CustomFrame.test.tsx b/superset-frontend/src/explore/components/controls/DateFilterControl/tests/CustomFrame.test.tsx index e5ed701f32d66..ab8fc5bb35bb4 100644 --- a/superset-frontend/src/explore/components/controls/DateFilterControl/tests/CustomFrame.test.tsx +++ b/superset-frontend/src/explore/components/controls/DateFilterControl/tests/CustomFrame.test.tsx @@ -274,6 +274,7 @@ test('should translate Date Picker', async () => { await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading')); userEvent.click(screen.getAllByRole('img', { name: 'calendar' })[0]); expect(screen.getByText('2021')).toBeInTheDocument(); + expect(screen.getByText('lu')).toBeInTheDocument(); expect(screen.getByText('ma')).toBeInTheDocument(); expect(screen.getByText('me')).toBeInTheDocument(); @@ -306,7 +307,7 @@ test('calls onChange when START Specific Date/Time is selected', async () => { const randomDate = screen.getByTitle('2021-03-11'); userEvent.click(randomDate); - const okButton = screen.getByText('Ok'); + const okButton = screen.getByText('OK'); userEvent.click(okButton); expect(onChange).toHaveBeenCalled(); @@ -335,7 +336,7 @@ test('calls onChange when END Specific Date/Time is selected', async () => { const randomDate = screen.getByTitle('2021-03-28'); userEvent.click(randomDate); - const okButton = screen.getByText('Ok'); + const okButton = screen.getByText('OK'); userEvent.click(okButton); expect(onChange).toHaveBeenCalled(); @@ -372,7 +373,7 @@ test('calls onChange when a date is picked from anchor mode date picker', async const randomDate = screen.getByTitle('2024-06-05'); userEvent.click(randomDate); - const okButton = screen.getByText('Ok'); + const okButton = screen.getByText('OK'); userEvent.click(okButton); expect(onChange).toHaveBeenCalled(); diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/utils/constants.ts b/superset-frontend/src/explore/components/controls/DateFilterControl/utils/constants.ts index dee66da7d99d3..4a116fb65e444 100644 --- a/superset-frontend/src/explore/components/controls/DateFilterControl/utils/constants.ts +++ b/superset-frontend/src/explore/components/controls/DateFilterControl/utils/constants.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -import moment from 'moment'; import { t } from '@superset-ui/core'; import { SelectOptionType, @@ -32,6 +31,7 @@ import { CurrentQuarter, CurrentDay, } from 'src/explore/components/controls/DateFilterControl/types'; +import { extendedDayjs } from 'src/utils/dates'; export const FRAME_OPTIONS: SelectOptionType[] = [ { value: 'Common', label: t('Last') }, @@ -130,30 +130,16 @@ export const CURRENT_CALENDAR_RANGE_SET: Set = new Set([ CurrentYear, ]); -export const MOMENT_FORMAT = 'YYYY-MM-DD[T]HH:mm:ss'; -export const SEVEN_DAYS_AGO = moment() +export const DAYJS_FORMAT = 'YYYY-MM-DD[T]HH:mm:ss'; +export const SEVEN_DAYS_AGO = extendedDayjs() .utc() .startOf('day') .subtract(7, 'days') - .format(MOMENT_FORMAT); -export const MIDNIGHT = moment().utc().startOf('day').format(MOMENT_FORMAT); - -export const LOCALE_MAPPING = { - en: () => import('antd/lib/date-picker/locale/en_US'), - fr: () => import('antd/lib/date-picker/locale/fr_FR'), - es: () => import('antd/lib/date-picker/locale/es_ES'), - it: () => import('antd/lib/date-picker/locale/it_IT'), - zh: () => import('antd/lib/date-picker/locale/zh_CN'), - ja: () => import('antd/lib/date-picker/locale/ja_JP'), - de: () => import('antd/lib/date-picker/locale/de_DE'), - pt: () => import('antd/lib/date-picker/locale/pt_PT'), - pt_BR: () => import('antd/lib/date-picker/locale/pt_BR'), - ru: () => import('antd/lib/date-picker/locale/ru_RU'), - ko: () => import('antd/lib/date-picker/locale/ko_KR'), - sk: () => import('antd/lib/date-picker/locale/sk_SK'), - sl: () => import('antd/lib/date-picker/locale/sl_SI'), - nl: () => import('antd/lib/date-picker/locale/nl_NL'), -}; + .format(DAYJS_FORMAT); +export const MIDNIGHT = extendedDayjs() + .utc() + .startOf('day') + .format(DAYJS_FORMAT); export enum DateFilterTestKey { CommonFrame = 'common-frame', diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/utils/dateParser.ts b/superset-frontend/src/explore/components/controls/DateFilterControl/utils/dateParser.ts index 07a946a315496..2f3256dd394ee 100644 --- a/superset-frontend/src/explore/components/controls/DateFilterControl/utils/dateParser.ts +++ b/superset-frontend/src/explore/components/controls/DateFilterControl/utils/dateParser.ts @@ -16,10 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -// TODO: @msyavuz - Replace this with dayjs after datepicker migration -import moment, { Moment } from 'moment'; +import { extendedDayjs } from 'src/utils/dates'; +import { Dayjs } from 'dayjs'; import { CustomRangeType } from 'src/explore/components/controls/DateFilterControl/types'; -import { MOMENT_FORMAT } from './constants'; +import { DAYJS_FORMAT } from './constants'; /** * RegExp to test a string for a full ISO 8601 Date @@ -39,18 +39,18 @@ export const ISO8601_AND_CONSTANT = RegExp( const SPECIFIC_MODE = ['specific', 'today', 'now']; -export const dttmToMoment = (dttm: string): Moment => { +export const dttmToDayjs = (dttm: string): Dayjs => { if (dttm === 'now') { - return moment().utc().startOf('second'); + return extendedDayjs().utc().startOf('second'); } if (dttm === 'today') { - return moment().utc().startOf('day'); + return extendedDayjs().utc().startOf('day'); } - return moment(dttm); + return extendedDayjs(dttm); }; export const dttmToString = (dttm: string): string => - dttmToMoment(dttm).format(MOMENT_FORMAT); + dttmToDayjs(dttm).format(DAYJS_FORMAT); export const customTimeRangeEncode = (customRange: CustomRangeType): string => { const { diff --git a/superset-frontend/src/explore/components/controls/TimeOffsetControl.test.tsx b/superset-frontend/src/explore/components/controls/TimeOffsetControl.test.tsx index 6745e34cf332a..55facc590e417 100644 --- a/superset-frontend/src/explore/components/controls/TimeOffsetControl.test.tsx +++ b/superset-frontend/src/explore/components/controls/TimeOffsetControl.test.tsx @@ -21,8 +21,8 @@ import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import { Provider } from 'react-redux'; import { ThemeProvider, supersetTheme } from '@superset-ui/core'; -import moment from 'moment'; import { INVALID_DATE } from '@superset-ui/chart-controls'; +import { extendedDayjs } from 'src/utils/dates'; import TimeOffsetControls, { TimeOffsetControlsProps, } from './TimeOffsetControl'; @@ -73,8 +73,8 @@ describe('TimeOffsetControls', () => { // Our Time comparison control depends on this string for supporting date deletion on date picker // That's why this test is linked to the TimeOffsetControl component - it('Moment should return "Invalid date" when parsing an invalid date string', () => { - const invalidDate = moment('not-a-date'); + it('Dayjs should return "Invalid date" when parsing an invalid date string', () => { + const invalidDate = extendedDayjs('not-a-date'); expect(invalidDate.format()).toBe(INVALID_DATE); }); }); diff --git a/superset-frontend/src/explore/components/controls/TimeOffsetControl.tsx b/superset-frontend/src/explore/components/controls/TimeOffsetControl.tsx index c799db64f3628..f85872104c68f 100644 --- a/superset-frontend/src/explore/components/controls/TimeOffsetControl.tsx +++ b/superset-frontend/src/explore/components/controls/TimeOffsetControl.tsx @@ -18,8 +18,7 @@ */ import { ReactNode, useCallback, useEffect, useState } from 'react'; import { isEmpty, isEqual } from 'lodash'; -// TODO: @msyavuz - Replace with dayjs when migrating datpicker to antd5 -import moment, { Moment } from 'moment'; +import { extendedDayjs } from 'src/utils/dates'; import { parseDttmToDate, BinaryAdhocFilter, @@ -29,8 +28,8 @@ import { computeCustomDateTime, fetchTimeRange, } from '@superset-ui/core'; -import { DatePicker } from 'antd'; -import { RangePickerProps } from 'antd/lib/date-picker'; +import { DatePicker } from 'src/components/DatePicker'; +import { RangePickerProps } from 'antd-v5/es/date-picker'; import { useSelector } from 'react-redux'; import ControlHeader from 'src/explore/components/ControlHeader'; @@ -39,16 +38,17 @@ import { DEFAULT_DATE_PATTERN, INVALID_DATE, } from '@superset-ui/chart-controls'; +import { Dayjs } from 'dayjs'; export interface TimeOffsetControlsProps { label?: ReactNode; startDate?: string; description?: string; hovered?: boolean; - value?: Moment; + value?: Dayjs; onChange: (datetime: string) => void; } -const MOMENT_FORMAT = 'YYYY-MM-DD'; +const DAYJS_FORMAT = 'YYYY-MM-DD'; const isTimeRangeEqual = ( left: BinaryAdhocFilter[], @@ -62,14 +62,14 @@ export default function TimeOffsetControls({ ...props }: TimeOffsetControlsProps) { const [startDate, setStartDate] = useState(''); - const [formatedDate, setFormatedDate] = useState( + const [formatedDate, setFormatedDate] = useState( undefined, ); const [customStartDateInFilter, setCustomStartDateInFilter] = useState< - moment.Moment | undefined + Dayjs | undefined >(undefined); const [formatedFilterDate, setFormatedFilterDate] = useState< - moment.Moment | undefined + Dayjs | undefined >(undefined); const [savedStartDate, setSavedStartDate] = useState(null); const [isDateSelected, setIsDateSelected] = useState(true); @@ -92,7 +92,7 @@ export default function TimeOffsetControls({ if (savedStartDate !== currentStartDate) { setSavedStartDate(currentStartDate); if (currentStartDate !== INVALID_DATE) { - onChange(moment(currentStartDate).format(MOMENT_FORMAT)); + onChange(extendedDayjs(currentStartDate).format(DAYJS_FORMAT)); setIsDateSelected(true); } else { setIsDateSelected(false); @@ -132,7 +132,7 @@ export default function TimeOffsetControls({ ); } customStartDate?.setHours(0, 0, 0, 0); - setCustomStartDateInFilter(moment(customStartDate)); + setCustomStartDateInFilter(extendedDayjs(customStartDate)); } else { setCustomStartDateInFilter(undefined); } @@ -149,11 +149,11 @@ export default function TimeOffsetControls({ const dates = res?.value?.match(DEFAULT_DATE_PATTERN); const [startDate, endDate] = dates ?? []; customTimeRange(`${startDate} : ${endDate}` ?? ''); - setFormatedFilterDate(moment(parseDttmToDate(startDate))); + setFormatedFilterDate(extendedDayjs(parseDttmToDate(startDate))); }); } else { setCustomStartDateInFilter(undefined); - setFormatedFilterDate(moment(parseDttmToDate(''))); + setFormatedFilterDate(extendedDayjs(parseDttmToDate(''))); } }, [currentTimeRangeFilters, customTimeRange]); @@ -169,15 +169,15 @@ export default function TimeOffsetControls({ } if (customStartDateInFilter) { setStartDate(customStartDateInFilter.toString()); - setFormatedDate(moment(customStartDateInFilter)); + setFormatedDate(extendedDayjs(customStartDateInFilter)); } else if (date) { setStartDate(date); - setFormatedDate(moment(parseDttmToDate(date))); + setFormatedDate(extendedDayjs(parseDttmToDate(date))); } } else if (savedStartDate) { if (savedStartDate !== INVALID_DATE) { setStartDate(savedStartDate); - setFormatedDate(moment(parseDttmToDate(savedStartDate))); + setFormatedDate(extendedDayjs(parseDttmToDate(savedStartDate))); } } }, [previousCustomFilter, savedStartDate, customStartDateInFilter]); @@ -186,12 +186,12 @@ export default function TimeOffsetControls({ // When switching offsets from inherit and the previous custom is no longer valid if (customStartDateInFilter) { if (formatedDate && formatedDate > customStartDateInFilter) { - const resetDate = moment + const resetDate = extendedDayjs .utc(customStartDateInFilter) .subtract(1, 'day'); setStartDate(resetDate.toString()); setFormatedDate(resetDate); - onChange(moment.utc(resetDate).format(MOMENT_FORMAT)); + onChange(extendedDayjs.utc(resetDate).format(DAYJS_FORMAT)); setIsDateSelected(true); } } @@ -200,10 +200,12 @@ export default function TimeOffsetControls({ formatedFilterDate && formatedDate > formatedFilterDate ) { - const resetDate = moment.utc(formatedFilterDate).subtract(1, 'day'); + const resetDate = extendedDayjs + .utc(formatedFilterDate) + .subtract(1, 'day'); setStartDate(resetDate.toString()); setFormatedDate(resetDate); - onChange(moment.utc(resetDate).format(MOMENT_FORMAT)); + onChange(extendedDayjs.utc(resetDate).format(DAYJS_FORMAT)); setIsDateSelected(true); } }, [formatedFilterDate, formatedDate, customStartDateInFilter]); @@ -214,7 +216,7 @@ export default function TimeOffsetControls({ ? current && current > formatedFilterDate : false; } - return current && current > moment(customStartDateInFilter); + return current && current > extendedDayjs(customStartDateInFilter); }; return startDate || formatedDate ? ( @@ -224,15 +226,15 @@ export default function TimeOffsetControls({ css={css` width: 100%; `} - onChange={(datetime: Moment) => - onChange(datetime ? datetime.format(MOMENT_FORMAT) : '') + onChange={(datetime: Dayjs) => + onChange(datetime ? datetime.format(DAYJS_FORMAT) : '') } defaultPickerValue={ - startDate ? moment(formatedDate).subtract(1, 'day') : undefined + startDate ? extendedDayjs(formatedDate).subtract(1, 'day') : undefined } disabledDate={disabledDate} - defaultValue={moment(formatedDate)} - value={isDateSelected ? moment(formatedDate) : null} + defaultValue={extendedDayjs(formatedDate)} + value={isDateSelected ? extendedDayjs(formatedDate) : null} /> ) : null; 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 d1e407100b94f..57074a2b1868b 100644 --- a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.tsx +++ b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.tsx @@ -165,14 +165,14 @@ describe('VizTypeControl', () => { it('Render viz tiles when non-featured is rendered', async () => { const props = { ...defaultProps, - value: 'line', + value: VizType.Sankey, isModalOpenInit: false, }; const state = { charts: { 1: { latestQueryFormData: { - viz_type: VizType.LegacyLine, + viz_type: VizType.Sankey, }, }, }, diff --git a/superset-frontend/src/explore/components/controls/ZoomConfigControl/ZoomConfigsChart.tsx b/superset-frontend/src/explore/components/controls/ZoomConfigControl/ZoomConfigsChart.tsx index 0d4b44b53202b..d3f755bd03c5a 100644 --- a/superset-frontend/src/explore/components/controls/ZoomConfigControl/ZoomConfigsChart.tsx +++ b/superset-frontend/src/explore/components/controls/ZoomConfigControl/ZoomConfigsChart.tsx @@ -17,7 +17,7 @@ * under the License. */ import { t } from '@superset-ui/core'; -import * as echarts from 'echarts'; +import { init as echartsInit } from 'echarts'; import { createRef, FC, useEffect } from 'react'; import { ZoomConfigsChartProps } from './types'; import { @@ -48,7 +48,7 @@ export const ZoomConfigsChart: FC = ({ const barWidth = 15; const data = zoomConfigsToData(value.values); - const chart = echarts.init(ref.current); + const chart = echartsInit(ref.current); const option = { xAxis: { diff --git a/superset-frontend/src/explore/components/controls/ZoomConfigControl/zoomUtil.ts b/superset-frontend/src/explore/components/controls/ZoomConfigControl/zoomUtil.ts index 281c7aba5a2ec..6dce39c21e1f0 100644 --- a/superset-frontend/src/explore/components/controls/ZoomConfigControl/zoomUtil.ts +++ b/superset-frontend/src/explore/components/controls/ZoomConfigControl/zoomUtil.ts @@ -17,7 +17,7 @@ * under the License. */ -import * as echarts from 'echarts'; +import { util } from 'echarts'; import { isZoomConfigsFixed, isZoomConfigsLinear } from './typeguards'; import { CreateDragGraphicOption, @@ -105,10 +105,10 @@ export const createDragGraphicOption = ({ // Give a big z value, which makes the circle cover the symbol // in bar series. z: 100, - // Util method `echarts.util.curry` is used here to generate a + // Util method `util.curry` (from echarts) is used here to generate a // new function the same as `onDrag`, except that the // first parameter is fixed to be the `dataIndex` here. - ondrag: echarts.util.curry(onDrag, dataIndex), + ondrag: util.curry(onDrag, dataIndex), }; }; diff --git a/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/DashboardsSubMenu.test.tsx b/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/DashboardsSubMenu.test.tsx index e297b66885af8..c6e103702a9b3 100644 --- a/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/DashboardsSubMenu.test.tsx +++ b/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/DashboardsSubMenu.test.tsx @@ -21,57 +21,58 @@ import userEvent from '@testing-library/user-event'; import { Menu } from 'src/components/Menu'; import DashboardItems from './DashboardsSubMenu'; -const asyncRender = (numberOfItems: number) => - waitFor(() => { - const dashboards = []; - for (let i = 1; i <= numberOfItems; i += 1) { - dashboards.push({ id: i, dashboard_title: `Dashboard ${i}` }); - } - render( - - - - - , - { - useRouter: true, - }, - ); - }); +const asyncRender = (numberOfItems: number) => { + const dashboards = []; + for (let i = 1; i <= numberOfItems; i += 1) { + dashboards.push({ id: i, dashboard_title: `Dashboard ${i}` }); + } + render( + + + + + , + { + useRouter: true, + }, + ); +}; test('renders a submenu', async () => { - await asyncRender(3); - expect(screen.getByText('Dashboard 1')).toBeInTheDocument(); - expect(screen.getByText('Dashboard 2')).toBeInTheDocument(); - expect(screen.getByText('Dashboard 3')).toBeInTheDocument(); + asyncRender(3); + await waitFor(() => { + expect(screen.getByText('Dashboard 1')).toBeInTheDocument(); + expect(screen.getByText('Dashboard 2')).toBeInTheDocument(); + expect(screen.getByText('Dashboard 3')).toBeInTheDocument(); + }); }); test('renders a submenu with search', async () => { - await asyncRender(20); - expect(screen.getByPlaceholderText('Search')).toBeInTheDocument(); + asyncRender(20); + expect(await screen.findByPlaceholderText('Search')).toBeInTheDocument(); }); test('displays a searched value', async () => { - await asyncRender(20); + asyncRender(20); userEvent.type(screen.getByPlaceholderText('Search'), '2'); - expect(screen.getByText('Dashboard 2')).toBeInTheDocument(); - expect(screen.getByText('Dashboard 20')).toBeInTheDocument(); + expect(await screen.findByText('Dashboard 2')).toBeInTheDocument(); + expect(await screen.findByText('Dashboard 20')).toBeInTheDocument(); }); test('renders a "No results found" message when searching', async () => { - await asyncRender(20); + asyncRender(20); userEvent.type(screen.getByPlaceholderText('Search'), 'unknown'); - expect(screen.getByText('No results found')).toBeInTheDocument(); + expect(await screen.findByText('No results found')).toBeInTheDocument(); }); test('renders a submenu with no dashboards', async () => { - await asyncRender(0); - expect(screen.getByText('None')).toBeInTheDocument(); + asyncRender(0); + expect(await screen.findByText('None')).toBeInTheDocument(); }); test('shows link icon when hovering', async () => { - await asyncRender(3); + asyncRender(3); expect(screen.queryByRole('img', { name: 'full' })).not.toBeInTheDocument(); - userEvent.hover(screen.getByText('Dashboard 1')); - expect(screen.getByRole('img', { name: 'full' })).toBeInTheDocument(); + userEvent.hover(await screen.findByText('Dashboard 1')); + expect(await screen.findByRole('img', { name: 'full' })).toBeInTheDocument(); }); diff --git a/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/index.jsx b/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/index.jsx index f443ab750f4a2..21cf69cec4174 100644 --- a/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/index.jsx +++ b/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/index.jsx @@ -97,7 +97,7 @@ export const MenuTrigger = styled(Button)` padding: 0; border: 1px solid ${theme.colors.primary.dark2}; - &.ant-btn > span.anticon { + &.antd5-btn > span.anticon { line-height: 0; transition: inherit; } @@ -384,7 +384,7 @@ export const useExploreAdditionalActionsMenu = ( {t('Embed code')} +
{t('Embed code')}
} modalTitle={t('Embed code')} modalBody={ @@ -429,7 +429,7 @@ export const useExploreAdditionalActionsMenu = ( {t('View query')} +
{t('View query')}
} modalTitle={t('View query')} modalBody={ diff --git a/superset-frontend/src/features/alerts/AlertReportModal.test.tsx b/superset-frontend/src/features/alerts/AlertReportModal.test.tsx index 0673bf5fd05d2..6c9750037e696 100644 --- a/superset-frontend/src/features/alerts/AlertReportModal.test.tsx +++ b/superset-frontend/src/features/alerts/AlertReportModal.test.tsx @@ -597,6 +597,7 @@ test('renders all notification fields', async () => { expect(recipients).toBeInTheDocument(); expect(addNotificationMethod).toBeInTheDocument(); }); + test('adds another notification method section after clicking add notification method', async () => { render(, { useRedux: true, diff --git a/superset-frontend/src/features/annotations/AnnotationModal.tsx b/superset-frontend/src/features/annotations/AnnotationModal.tsx index fcb105af50a97..e4c4488986f2c 100644 --- a/superset-frontend/src/features/annotations/AnnotationModal.tsx +++ b/superset-frontend/src/features/annotations/AnnotationModal.tsx @@ -21,8 +21,7 @@ import { FunctionComponent, useState, useEffect, ChangeEvent } from 'react'; import { styled, t } from '@superset-ui/core'; import { useSingleViewResource } from 'src/views/CRUD/hooks'; import { RangePicker } from 'src/components/DatePicker'; -// TODO: @msyavuz - Remove this after datepicker -import moment from 'moment'; +import { extendedDayjs } from 'src/utils/dates'; import Icons from 'src/components/Icons'; import Modal from 'src/components/Modal'; import { StyledIcon } from 'src/views/CRUD/utils'; @@ -199,18 +198,23 @@ const AnnotationModal: FunctionComponent = ({ setCurrentAnnotation(data); }; - const onDateChange = (value: any, dateString: Array) => { + const onDateChange = (dates: any, dateString: Array) => { + if (!dates?.[0] || !dates?.[1]) { + const data = { + ...currentAnnotation, + start_dttm: '', + end_dttm: '', + short_descr: currentAnnotation?.short_descr ?? '', + }; + setCurrentAnnotation(data); + return; + } + const data = { ...currentAnnotation, - end_dttm: - currentAnnotation && dateString[1].length - ? moment(dateString[1]).format('YYYY-MM-DD HH:mm') - : '', - short_descr: currentAnnotation ? currentAnnotation.short_descr : '', - start_dttm: - currentAnnotation && dateString[0].length - ? moment(dateString[0]).format('YYYY-MM-DD HH:mm') - : '', + start_dttm: dates[0].format('YYYY-MM-DD HH:mm'), + end_dttm: dates[1].format('YYYY-MM-DD HH:mm'), + short_descr: currentAnnotation?.short_descr ?? '', }; setCurrentAnnotation(data); }; @@ -305,15 +309,15 @@ const AnnotationModal: FunctionComponent = ({ { ]; visibleComponents.forEach(component => { - expect(component).toBeVisible(); + expect(component).toBeInTheDocument(); }); }); @@ -781,7 +781,7 @@ describe('DatabaseModal', () => { enableRowExpansionCheckbox, ]; visibleComponents.forEach(component => { - expect(component).toBeVisible(); + expect(component).toBeInTheDocument(); }); invisibleComponents.forEach(component => { expect(component).not.toBeVisible(); @@ -849,7 +849,7 @@ describe('DatabaseModal', () => { ]; visibleComponents.forEach(component => { - expect(component).toBeVisible(); + expect(component).toBeInTheDocument(); }); }); @@ -929,7 +929,7 @@ describe('DatabaseModal', () => { // ---------- Assertions ---------- visibleComponents.forEach(component => { - expect(component).toBeVisible(); + expect(component).toBeInTheDocument(); }); invisibleComponents.forEach(component => { expect(component).not.toBeVisible(); @@ -1137,7 +1137,7 @@ describe('DatabaseModal', () => { expect(await screen.findByText(/step 2 of 2/i)).toBeInTheDocument(); const sqlAlchemyFormStepText = screen.getByText(/step 2 of 2/i); - expect(sqlAlchemyFormStepText).toBeVisible(); + expect(sqlAlchemyFormStepText).toBeInTheDocument(); }); describe('SQL Alchemy form flow', () => { @@ -1293,7 +1293,7 @@ describe('DatabaseModal', () => { expect(await screen.findByText(/step 2 of 2/i)).toBeInTheDocument(); const SSHTunnelingToggle = screen.getByTestId('ssh-tunnel-switch'); - expect(SSHTunnelingToggle).toBeVisible(); + expect(SSHTunnelingToggle).toBeInTheDocument(); const SSHTunnelServerAddressInput = screen.queryByTestId( 'ssh-tunnel-server_address-input', ); @@ -1325,26 +1325,26 @@ describe('DatabaseModal', () => { const SSHTunnelUsePasswordInput = screen.getByTestId( 'ssh-tunnel-use_password-radio', ); - expect(SSHTunnelUsePasswordInput).toBeVisible(); + expect(SSHTunnelUsePasswordInput).toBeInTheDocument(); const SSHTunnelUsePrivateKeyInput = screen.getByTestId( 'ssh-tunnel-use_private_key-radio', ); - expect(SSHTunnelUsePrivateKeyInput).toBeVisible(); + expect(SSHTunnelUsePrivateKeyInput).toBeInTheDocument(); const SSHTunnelPasswordInput = screen.getByTestId( 'ssh-tunnel-password-input', ); // By default, we use Password as login method - expect(SSHTunnelPasswordInput).toBeVisible(); + expect(SSHTunnelPasswordInput).toBeInTheDocument(); // Change the login method to use private key userEvent.click(SSHTunnelUsePrivateKeyInput); const SSHTunnelPrivateKeyInput = screen.getByTestId( 'ssh-tunnel-private_key-input', ); - expect(SSHTunnelPrivateKeyInput).toBeVisible(); + expect(SSHTunnelPrivateKeyInput).toBeInTheDocument(); const SSHTunnelPrivateKeyPasswordInput = screen.getByTestId( 'ssh-tunnel-private_key_password-input', ); - expect(SSHTunnelPrivateKeyPasswordInput).toBeVisible(); + expect(SSHTunnelPrivateKeyPasswordInput).toBeInTheDocument(); }); }); }); diff --git a/superset-frontend/src/features/databases/UploadDataModel/UploadDataModal.test.tsx b/superset-frontend/src/features/databases/UploadDataModel/UploadDataModal.test.tsx index 54b817654600d..7347e13bd5755 100644 --- a/superset-frontend/src/features/databases/UploadDataModel/UploadDataModal.test.tsx +++ b/superset-frontend/src/features/databases/UploadDataModel/UploadDataModal.test.tsx @@ -24,7 +24,6 @@ import { render, screen } from 'spec/helpers/testing-library'; import userEvent from '@testing-library/user-event'; import { waitFor } from '@testing-library/react'; import { UploadFile } from 'antd/lib/upload/interface'; -import { forEach } from 'lodash'; fetchMock.post('glob:*api/v1/database/1/csv_upload/', {}); fetchMock.post('glob:*api/v1/database/1/excel_upload/', {}); @@ -782,7 +781,7 @@ test('Columnar, form post', async () => { test('CSV, validate file extension returns false', () => { const invalidFileNames = ['out', 'out.exe', 'out.csv.exe', '.csv', 'out.xls']; - forEach(invalidFileNames, fileName => { + invalidFileNames.forEach(fileName => { const file: UploadFile = { name: fileName, uid: 'xp', @@ -795,7 +794,7 @@ test('CSV, validate file extension returns false', () => { test('Excel, validate file extension returns false', () => { const invalidFileNames = ['out', 'out.exe', 'out.xls.exe', '.csv', 'out.csv']; - forEach(invalidFileNames, fileName => { + invalidFileNames.forEach(fileName => { const file: UploadFile = { name: fileName, uid: 'xp', @@ -814,7 +813,7 @@ test('Columnar, validate file extension returns false', () => { '.parquet', 'out.excel', ]; - forEach(invalidFileNames, fileName => { + invalidFileNames.forEach(fileName => { const file: UploadFile = { name: fileName, uid: 'xp', @@ -827,7 +826,7 @@ test('Columnar, validate file extension returns false', () => { test('CSV, validate file extension returns true', () => { const invalidFileNames = ['out.csv', 'out.tsv', 'out.exe.csv', 'out a.csv']; - forEach(invalidFileNames, fileName => { + invalidFileNames.forEach(fileName => { const file: UploadFile = { name: fileName, uid: 'xp', @@ -840,7 +839,7 @@ test('CSV, validate file extension returns true', () => { test('Excel, validate file extension returns true', () => { const invalidFileNames = ['out.xls', 'out.xlsx', 'out.exe.xls', 'out a.xls']; - forEach(invalidFileNames, fileName => { + invalidFileNames.forEach(fileName => { const file: UploadFile = { name: fileName, uid: 'xp', @@ -858,7 +857,7 @@ test('Columnar, validate file extension returns true', () => { 'out.exe.zip', 'out a.parquet', ]; - forEach(invalidFileNames, fileName => { + invalidFileNames.forEach(fileName => { const file: UploadFile = { name: fileName, uid: 'xp', diff --git a/superset-frontend/src/features/datasets/styles.ts b/superset-frontend/src/features/datasets/styles.ts index 728aa12ae42d2..108504475ae73 100644 --- a/superset-frontend/src/features/datasets/styles.ts +++ b/superset-frontend/src/features/datasets/styles.ts @@ -118,7 +118,7 @@ export const StyledLayoutFooter = styled.div` `; export const HeaderComponentStyles = styled.div` - .ant-btn { + .antd5-btn { span { margin-right: 0; } diff --git a/superset-frontend/src/features/home/ChartTable.test.tsx b/superset-frontend/src/features/home/ChartTable.test.tsx index 79fe01e323c7f..f03a705245e81 100644 --- a/superset-frontend/src/features/home/ChartTable.test.tsx +++ b/superset-frontend/src/features/home/ChartTable.test.tsx @@ -33,7 +33,7 @@ const mockCharts = [...new Array(3)].map((_, i) => ({ id: i, slice_name: `cool chart ${i}`, url: 'url', - viz_type: VizType.LegacyBar, + viz_type: VizType.Bar, datasource_title: `ds${i}`, thumbnail_url: '', })); diff --git a/superset-frontend/src/features/home/DashboardTable.test.tsx b/superset-frontend/src/features/home/DashboardTable.test.tsx index 970dbc138a4d1..9fe6726fcb827 100644 --- a/superset-frontend/src/features/home/DashboardTable.test.tsx +++ b/superset-frontend/src/features/home/DashboardTable.test.tsx @@ -69,7 +69,7 @@ describe('DashboardTable', () => { }); it('render a submenu with clickable tabs and buttons', async () => { - expect(wrapper.find('SubMenu')).toExist(); + expect(wrapper.find('Menu')).toExist(); expect(wrapper.find('[role="tab"]')).toHaveLength(2); expect(wrapper.find('Button')).toHaveLength(6); act(() => { diff --git a/superset-frontend/src/features/home/EmptyState.test.tsx b/superset-frontend/src/features/home/EmptyState.test.tsx index 7506b3d0f12f2..fe633bc788632 100644 --- a/superset-frontend/src/features/home/EmptyState.test.tsx +++ b/superset-frontend/src/features/home/EmptyState.test.tsx @@ -62,11 +62,14 @@ describe('EmptyState', () => { tableName: WelcomeTable.Recents, }, ]; + variants.forEach(variant => { - it(`it renders an ${variant.tab} ${variant.tableName} empty state`, () => { + it(`renders an ${variant.tab} ${variant.tableName} empty state`, () => { const wrapper = mount(); expect(wrapper).toExist(); - const textContainer = wrapper.find('.ant-empty-description'); + + // Select the first description node + const textContainer = wrapper.find('.ant-empty-description').at(0); expect(textContainer.text()).toEqual( variant.tab === TableTab.Favorite ? "You don't have any favorites yet!" @@ -79,12 +82,19 @@ describe('EmptyState', () => { expect(wrapper.find('button')).toHaveLength(1); }); }); + recents.forEach(recent => { - it(`it renders a ${recent.tab} ${recent.tableName} empty state`, () => { + it(`renders a ${recent.tab} ${recent.tableName} empty state`, () => { const wrapper = mount(); expect(wrapper).toExist(); - const textContainer = wrapper.find('.ant-empty-description'); + + // Select the first description node + const textContainer = wrapper.find('.ant-empty-description').at(0); + + // Validate the image expect(wrapper.find('.ant-empty-image').children()).toHaveLength(1); + + // Check the correct text is displayed expect(textContainer.text()).toContain( `Recently ${recent.tab?.toLowerCase()} charts, dashboards, and saved queries will appear here`, ); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-histogram/Histogram.stories.tsx b/superset-frontend/src/features/home/LanguagePicker.stories.tsx similarity index 50% rename from superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-histogram/Histogram.stories.tsx rename to superset-frontend/src/features/home/LanguagePicker.stories.tsx index c6f3badcd7ba6..b73d23f0235ea 100644 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/legacy-plugin-chart-histogram/Histogram.stories.tsx +++ b/superset-frontend/src/features/home/LanguagePicker.stories.tsx @@ -1,4 +1,4 @@ -/* +/** * 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 @@ -16,33 +16,43 @@ * specific language governing permissions and limitations * under the License. */ - -/* eslint-disable no-magic-numbers */ -import { SuperChart, VizType } from '@superset-ui/core'; -import HistogramChartPlugin from '@superset-ui/legacy-plugin-chart-histogram'; -import data from './data'; - -new HistogramChartPlugin() - .configure({ key: VizType.LegacyHistogram }) - .register(); +import { MainNav as Menu } from 'src/components/Menu'; // Ensure correct import path +import LanguagePicker from './LanguagePicker'; // Ensure correct import path export default { - title: 'Legacy Chart Plugins/legacy-plugin-chart-histogram', + title: 'Components/LanguagePicker', + component: LanguagePicker, + parameters: { + docs: { + description: { + component: + 'The LanguagePicker component allows users to select a language from a dropdown.', + }, + }, + }, }; -export const basic = () => ( - +const mockedProps = { + locale: 'en', + languages: { + en: { + flag: 'us', + name: 'English', + url: '/lang/en', + }, + it: { + flag: 'it', + name: 'Italian', + url: '/lang/it', + }, + }, +}; + +const Template = (args: any) => ( + + + ); + +export const Default = Template.bind({}); +Default.args = mockedProps; diff --git a/superset-frontend/src/features/home/LanguagePicker.test.tsx b/superset-frontend/src/features/home/LanguagePicker.test.tsx index ad466eafc58a8..f846314a8c19a 100644 --- a/superset-frontend/src/features/home/LanguagePicker.test.tsx +++ b/superset-frontend/src/features/home/LanguagePicker.test.tsx @@ -43,7 +43,7 @@ test('should render', async () => { , ); - expect(await screen.findByRole('button')).toBeInTheDocument(); + expect(await screen.findByRole('menu')).toBeInTheDocument(); expect(container).toBeInTheDocument(); }); @@ -62,7 +62,7 @@ test('should render the items', async () => { , ); - userEvent.hover(screen.getByRole('button')); + userEvent.hover(screen.getByRole('menuitem')); expect(await screen.findByText('English')).toBeInTheDocument(); expect(await screen.findByText('Italian')).toBeInTheDocument(); }); diff --git a/superset-frontend/src/features/home/Menu.test.tsx b/superset-frontend/src/features/home/Menu.test.tsx index 123d12632f962..4a095472df127 100644 --- a/superset-frontend/src/features/home/Menu.test.tsx +++ b/superset-frontend/src/features/home/Menu.test.tsx @@ -327,8 +327,9 @@ test('should render the top navbar child menu items', async () => { useQueryParams: true, useRouter: true, }); - const sources = screen.getByText('Sources'); + const sources = await screen.findByText('Sources'); userEvent.hover(sources); + const datasets = await screen.findByText('Datasets'); const databases = await screen.findByText('Databases'); const dataset = menu[1].childs![0] as { url: string }; @@ -477,13 +478,13 @@ test('should render the About section and version_string, sha or build_number wh }); userEvent.hover(screen.getByText('Settings')); const about = await screen.findByText('About'); - const version = await screen.findByText(`Version: ${version_string}`); - const sha = await screen.findByText(`SHA: ${version_sha}`); - const build = await screen.findByText(`Build: ${build_number}`); + const version = await screen.findAllByText(`Version: ${version_string}`); + const sha = await screen.findAllByText(`SHA: ${version_sha}`); + const build = await screen.findAllByText(`Build: ${build_number}`); expect(about).toBeInTheDocument(); - expect(version).toBeInTheDocument(); - expect(sha).toBeInTheDocument(); - expect(build).toBeInTheDocument(); + expect(version[0]).toBeInTheDocument(); + expect(sha[0]).toBeInTheDocument(); + expect(build[0]).toBeInTheDocument(); }); test('should render the Documentation link when available', async () => { @@ -578,9 +579,15 @@ test('should render an extension component if one is supplied', async () => { setupExtensions(); - render(, { useRouter: true, useQueryParams: true }); + render(, { + useRouter: true, + useQueryParams: true, + useRedux: true, + }); + + const extension = await screen.findAllByText( + 'navbar.right extension component', + ); - expect( - await screen.findByText('navbar.right extension component'), - ).toBeInTheDocument(); + expect(extension[0]).toBeInTheDocument(); }); diff --git a/superset-frontend/src/features/home/Menu.tsx b/superset-frontend/src/features/home/Menu.tsx index 3d83aded7c0b7..ebcff4a023ce6 100644 --- a/superset-frontend/src/features/home/Menu.tsx +++ b/superset-frontend/src/features/home/Menu.tsx @@ -17,12 +17,11 @@ * under the License. */ import { useState, useEffect } from 'react'; -import { styled, css, useTheme, SupersetTheme } from '@superset-ui/core'; +import { styled } from '@superset-ui/core'; import { debounce } from 'lodash'; -import { Global } from '@emotion/react'; import { getUrlParam } from 'src/utils/urlUtils'; import { Row, Col, Grid } from 'src/components'; -import { MainNav as DropdownMenu, MenuMode } from 'src/components/Menu'; +import { MainNav, MenuMode } from 'src/components/Menu'; import { Tooltip } from 'src/components/Tooltip'; import { NavLink, useLocation } from 'react-router-dom'; import { GenericLink } from 'src/components/GenericLink/GenericLink'; @@ -99,92 +98,34 @@ const StyledHeader = styled.header` display: none; } } - .main-nav .ant-menu-submenu-title > svg { - top: ${theme.gridUnit * 5.25}px; - } @media (max-width: 767px) { .navbar-brand { float: none; } } - .ant-menu-horizontal .ant-menu-item { - height: 100%; - line-height: inherit; - } - .ant-menu > .ant-menu-item > a { - padding: ${theme.gridUnit * 4}px; - } @media (max-width: 767px) { - .ant-menu-item { + .antd5-menu-item { padding: 0 ${theme.gridUnit * 6}px 0 ${theme.gridUnit * 3}px !important; } - .ant-menu > .ant-menu-item > a { + .antd5-menu > .antd5-menu-item > span > a { padding: 0px; } - .main-nav .ant-menu-submenu-title > svg:nth-of-type(1) { + .main-nav .antd5-menu-submenu-title > svg:nth-of-type(1) { display: none; } - .ant-menu-item-active > a { - &:hover { - color: ${theme.colors.primary.base} !important; - background-color: transparent !important; - } - } - } - .ant-menu-item a { - &:hover { - color: ${theme.colors.grayscale.dark1}; - background-color: ${theme.colors.primary.light5}; - border-bottom: none; - margin: 0; - &:after { - opacity: 1; - width: 100%; - } - } } `} `; -const globalStyles = (theme: SupersetTheme) => css` - .ant-menu-submenu.ant-menu-submenu-popup.ant-menu.ant-menu-light.ant-menu-submenu-placement-bottomLeft { - border-radius: 0px; - } - .ant-menu-submenu.ant-menu-submenu-popup.ant-menu.ant-menu-light { - border-radius: 0px; - } - .ant-menu-vertical > .ant-menu-submenu.data-menu > .ant-menu-submenu-title { - height: 28px; - i { - padding-right: ${theme.gridUnit * 2}px; - margin-left: ${theme.gridUnit * 1.75}px; - } - } - .ant-menu-item-selected { - background-color: transparent; - &:not(.ant-menu-item-active) { - color: inherit; - border-bottom-color: transparent; - & > a { - color: inherit; - } - } - } - .ant-menu-horizontal > .ant-menu-item:has(> .is-active) { - color: ${theme.colors.primary.base}; - border-bottom-color: ${theme.colors.primary.base}; - & > a { - color: ${theme.colors.primary.base}; - } - } - .ant-menu-vertical > .ant-menu-item:has(> .is-active) { - background-color: ${theme.colors.primary.light5}; - & > a { - color: ${theme.colors.primary.base}; +const { SubMenu } = MainNav; + +const StyledSubMenu = styled(SubMenu)` + &.antd5-menu-submenu-active { + .antd5-menu-title-content { + color: ${({ theme }) => theme.colors.primary.base}; } } `; -const { SubMenu } = DropdownMenu; const { useBreakpoint } = Grid; @@ -201,7 +142,6 @@ export function Menu({ const [showMenu, setMenu] = useState('horizontal'); const screens = useBreakpoint(); const uiConfig = useUiConfig(); - const theme = useTheme(); useEffect(() => { function handleResize() { @@ -254,33 +194,33 @@ export function Menu({ }: MenuObjectProps) => { if (url && isFrontendRoute) { return ( - + {label} - + ); } if (url) { return ( - + {label} - + ); } return ( - : } > {childs?.map((child: MenuObjectChildProps | string, index1: number) => { if (typeof child === 'string' && child === '-' && label !== 'Data') { - return ; + return ; } if (typeof child !== 'string') { return ( - + {child.isFrontendRoute ? ( {child.label} )} - + ); } return null; })} - + ); }; return ( -
{isFrontendRoute(window.location.pathname) ? ( @@ -326,11 +265,12 @@ export function Menu({ {brand.text} )} - {menu.map((item, index) => { const props = { @@ -351,7 +291,7 @@ export function Menu({ return renderSubMenu(props); })} - + , { useRedux: true, useQueryParams: true, diff --git a/superset-frontend/src/features/home/RightMenu.tsx b/superset-frontend/src/features/home/RightMenu.tsx index e5c34fdd9e497..0c53c3abd65ba 100644 --- a/superset-frontend/src/features/home/RightMenu.tsx +++ b/superset-frontend/src/features/home/RightMenu.tsx @@ -33,7 +33,7 @@ import { getExtensionsRegistry, useTheme, } from '@superset-ui/core'; -import { MainNav as Menu } from 'src/components/Menu'; +import { Menu } from 'src/components/Menu'; import { Tooltip } from 'src/components/Tooltip'; import Icons from 'src/components/Icons'; import Label from 'src/components/Label'; @@ -71,21 +71,15 @@ const StyledI = styled.div` const styledDisabled = (theme: SupersetTheme) => css` color: ${theme.colors.grayscale.light1}; - .ant-menu-item-active { - color: ${theme.colors.grayscale.light1}; - cursor: default; - } `; const StyledDiv = styled.div<{ align: string }>` display: flex; + height: 100%; flex-direction: row; 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 StyledMenuItemWithIcon = styled.div` @@ -113,6 +107,14 @@ const styledChildMenu = (theme: SupersetTheme) => css` const { SubMenu } = Menu; +const StyledSubMenu = styled(SubMenu)` + &.antd5-menu-submenu-active { + .antd5-menu-title-content { + color: ${({ theme }) => theme.colors.primary.base}; + } + } +`; + const RightMenu = ({ align, settings, @@ -280,11 +282,8 @@ const RightMenu = ({ } }, [canDatabase, canDataset]); - const menuIconAndLabel = (menu: MenuObjectProps) => ( - <> - - {menu.label} - + const menuIcon = (menu: MenuObjectProps) => ( + ); const handleMenuSelection = (itemChose: any) => { @@ -407,10 +406,11 @@ const RightMenu = ({ mode="horizontal" onClick={handleMenuSelection} onOpenChange={onMenuOpen} + disabledOverflow > {RightMenuExtension && } {!navbarRight.user_is_anonymous && showActionDropdown && ( - @@ -424,10 +424,11 @@ const RightMenu = ({ if (menu.childs) { if (canShowChild) { return ( - {menu?.childs?.map?.((item, idx) => typeof item !== 'string' && item.name && item.perm ? ( @@ -437,7 +438,7 @@ const RightMenu = ({ ) : null, )} - + ); } if (!menu.url) { @@ -472,9 +473,9 @@ const RightMenu = ({ ) ); })} - + )} - } > @@ -548,7 +549,7 @@ const RightMenu = ({ , ]} - + {navbarRight.show_language_picker && ( { ]; setup({ buttons }); const testButton = screen.getByText(buttons[0].name); - expect(await screen.findAllByRole('button')).toHaveLength(3); + expect(await screen.findAllByRole('button')).toHaveLength(2); userEvent.click(testButton); expect(mockFunc).toHaveBeenCalled(); }); diff --git a/superset-frontend/src/features/home/SubMenu.tsx b/superset-frontend/src/features/home/SubMenu.tsx index 55b5f08f9b120..af1df5a59a391 100644 --- a/superset-frontend/src/features/home/SubMenu.tsx +++ b/superset-frontend/src/features/home/SubMenu.tsx @@ -24,7 +24,7 @@ import cx from 'classnames'; import { Tooltip } from 'src/components/Tooltip'; import { debounce } from 'lodash'; import { Row } from 'src/components'; -import { Menu, MenuMode, MainNav as DropdownMenu } from 'src/components/Menu'; +import { Menu, MenuMode, MainNav } from 'src/components/Menu'; import Button, { OnClickHandler } from 'src/components/Button'; import Icons from 'src/components/Icons'; import { MenuObjectProps } from 'src/types/bootstrapTypes'; @@ -48,7 +48,7 @@ const StyledHeader = styled.div` float: right; position: absolute; right: 0; - ul.ant-menu-root { + ul.antd5-menu-root { padding: 0px; } li[role='menuitem'] { @@ -69,78 +69,28 @@ const StyledHeader = styled.div` } .menu { background-color: ${({ theme }) => theme.colors.grayscale.light5}; - .ant-menu-horizontal { - line-height: inherit; - .ant-menu-item { - border-bottom: none; - &:hover { - border-bottom: none; - text-decoration: none; - } - } - } - .ant-menu { - padding: ${({ theme }) => theme.gridUnit * 4}px 0px; - } - } - - .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item { - margin: 0 ${({ theme }) => theme.gridUnit + 1}px; } - .menu .ant-menu-item { - li, - div { - a, - div { - font-size: ${({ theme }) => theme.typography.sizes.s}px; - color: ${({ theme }) => theme.colors.secondary.dark1}; - - a { - margin: 0; - padding: ${({ theme }) => theme.gridUnit * 2}px - ${({ theme }) => theme.gridUnit * 4}px; - line-height: ${({ theme }) => theme.gridUnit * 5}px; - - &:hover { - text-decoration: none; - } - } - } - - &.no-router a { - padding: ${({ theme }) => theme.gridUnit * 2}px - ${({ theme }) => theme.gridUnit * 4}px; - } + .menu > .antd5-menu { + padding: ${({ theme }) => theme.gridUnit * 5}px + ${({ theme }) => theme.gridUnit * 8}px; - &.active a { - background: ${({ theme }) => theme.colors.secondary.light4}; - border-radius: ${({ theme }) => theme.borderRadius}px; - } - } - - li.active > a, - li.active > div, - div.active > div, - li > a:hover, - li > a:focus, - li > div:hover, - div > div:hover, - div > a:hover { - background: ${({ theme }) => theme.colors.secondary.light4}; - border-bottom: none; + .antd5-menu-item { border-radius: ${({ theme }) => theme.borderRadius}px; - margin-bottom: ${({ theme }) => theme.gridUnit * 2}px; - text-decoration: none; + font-size: ${({ theme }) => theme.typography.sizes.s}px; + padding: ${({ theme }) => theme.gridUnit}px + ${({ theme }) => theme.gridUnit * 4}px; + margin-right: ${({ theme }) => theme.gridUnit}px; + } + .antd5-menu-item:hover, + .antd5-menu-item:has(> span > .active) { + background-color: ${({ theme }) => theme.colors.secondary.light4}; } } .btn-link { padding: 10px 0; } - .ant-menu-horizontal { - border: none; - } @media (max-width: 767px) { .header, .nav-right { @@ -148,17 +98,6 @@ const StyledHeader = styled.div` margin-left: ${({ theme }) => theme.gridUnit * 2}px; } } - .ant-menu-submenu { - span[role='img'] { - position: absolute; - right: ${({ theme }) => -theme.gridUnit + -2}px; - top: ${({ theme }) => theme.gridUnit + 1}px !important; - } - } - .dropdown-menu-links > div.ant-menu-submenu-title, - .ant-menu-submenu-open.ant-menu-submenu-active > div.ant-menu-submenu-title { - color: ${({ theme }) => theme.colors.primary.dark1}; - } `; const styledDisabled = (theme: SupersetTheme) => css` @@ -169,7 +108,7 @@ const styledDisabled = (theme: SupersetTheme) => css` color: ${theme.colors.grayscale.light1}; } - .ant-menu-item-selected { + .antd5-menu-item-selected { background-color: ${theme.colors.grayscale.light1}; } `; @@ -210,7 +149,7 @@ export interface SubMenuProps { dropDownLinks?: Array; } -const { SubMenu } = DropdownMenu; +const { SubMenu } = MainNav; const SubMenuComponent: FunctionComponent = props => { const [showMenu, setMenu] = useState('horizontal'); @@ -255,7 +194,7 @@ const SubMenuComponent: FunctionComponent = props => { {props.name &&
{props.name}
} - + {props.tabs?.map(tab => { if ((props.usesRouter || hasHistory) && !!tab.usesRouter) { return ( @@ -290,7 +229,7 @@ const SubMenuComponent: FunctionComponent = props => { })}
- + {props.dropDownLinks?.map((link, i) => ( = props => { {link.childs?.map(item => { if (typeof item === 'object') { return item.disable ? ( - = props => { > {item.label} - + ) : ( - + {item.label} - + ); } return null; diff --git a/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.test.tsx b/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.test.tsx index a2790f4740352..18d0d620ec05b 100644 --- a/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.test.tsx +++ b/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.test.tsx @@ -26,7 +26,7 @@ let isFeatureEnabledMock: jest.MockInstance; const createProps = () => ({ dashboardId: 1, useTextMenu: false, - isDropdownVisible: false, + isDropdownVisible: true, setIsDropdownVisible: jest.fn, setShowReportSubMenu: jest.fn, }); diff --git a/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.tsx b/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.tsx index aedf477442866..66a8195f1f187 100644 --- a/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.tsx +++ b/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.tsx @@ -56,7 +56,7 @@ const deleteColor = (theme: SupersetTheme) => css` `; const onMenuHover = (theme: SupersetTheme) => css` - & .ant-menu-item { + & .antd5-menu-item { padding: 5px 12px; margin-top: 0px; margin-bottom: 4px; @@ -235,25 +235,23 @@ export default function HeaderReportDropDown({ ) : ( - isDropdownVisible && ( - - toggleActiveKey(report, !isReportActive)} - > - - - {t('Email reports active')} - - - - {t('Edit email report')} - - - {t('Delete email report')} - - - ) + + toggleActiveKey(report, !isReportActive)} + > + + + {t('Email reports active')} + + + + {t('Edit email report')} + + + {t('Delete email report')} + + ); const menu = () => ( @@ -326,7 +324,7 @@ export default function HeaderReportDropDown({ dashboardId ? CreationMethod.Dashboards : CreationMethod.Charts } /> - {useTextMenu ? textMenu() : iconMenu()} + {isDropdownVisible ? (useTextMenu ? textMenu() : iconMenu()) : null} {currentReportDeleting && ( { + afterEach(() => { + fetchMock.reset(); + jest.clearAllMocks(); + }); + + test('should render', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); + }); + + test('renders the correct title and message', () => { + render(); + expect( + screen.getByText(/you are adding tags to 2 dashboards/i), + ).toBeInTheDocument(); + expect(screen.getByText('Bulk tag')).toBeInTheDocument(); + }); + + test('renders tags input field', async () => { + render(); + const tagsInput = await screen.findByRole('combobox', { name: /tags/i }); + expect(tagsInput).toBeInTheDocument(); + }); + + test('calls onHide when the Cancel button is clicked', () => { + render(); + const cancelButton = screen.getByText('Cancel'); + fireEvent.click(cancelButton); + expect(mockedProps.onHide).toHaveBeenCalled(); + }); + + test('submits the selected tags and shows success toast', async () => { + fetchMock.post('glob:*/api/v1/tag/bulk_create', { + result: { + objects_tagged: [1, 2], + objects_skipped: [], + }, + }); + + render(); + + const tagsInput = await screen.findByRole('combobox', { name: /tags/i }); + fireEvent.change(tagsInput, { target: { value: 'Test Tag' } }); + fireEvent.keyDown(tagsInput, { key: 'Enter', code: 'Enter' }); + + fireEvent.click(screen.getByText('Save')); + + await waitFor(() => { + expect(mockedProps.addSuccessToast).toHaveBeenCalledWith( + 'Tagged 2 dashboards', + ); + }); + + expect(mockedProps.refreshData).toHaveBeenCalled(); + expect(mockedProps.onHide).toHaveBeenCalled(); + }); + + test('handles API errors gracefully', async () => { + fetchMock.post('glob:*/api/v1/tag/bulk_create', 500); + + render(); + + const tagsInput = await screen.findByRole('combobox', { name: /tags/i }); + fireEvent.change(tagsInput, { target: { value: 'Test Tag' } }); + fireEvent.keyDown(tagsInput, { key: 'Enter', code: 'Enter' }); + + fireEvent.click(screen.getByText('Save')); + + await waitFor(() => { + expect(mockedProps.addDangerToast).toHaveBeenCalledWith( + 'Failed to tag items', + ); + }); + }); +}); diff --git a/superset-frontend/src/features/tags/BulkTagModal.tsx b/superset-frontend/src/features/tags/BulkTagModal.tsx index 32ccd4b0dde69..3195461285365 100644 --- a/superset-frontend/src/features/tags/BulkTagModal.tsx +++ b/superset-frontend/src/features/tags/BulkTagModal.tsx @@ -59,7 +59,7 @@ const BulkTagModal: FC = ({ endpoint: `/api/v1/tag/bulk_create`, jsonPayload: { tags: tags.map(tag => ({ - name: tag.value, + name: tag.label, objects_to_tag: selected.map(item => [ resourceName, +item.original.id, diff --git a/superset-frontend/src/hooks/useLocale.ts b/superset-frontend/src/hooks/useLocale.ts new file mode 100644 index 0000000000000..2c4ff3ce2f39d --- /dev/null +++ b/superset-frontend/src/hooks/useLocale.ts @@ -0,0 +1,83 @@ +/** + * 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 { Locale } from 'antd-v5/es/locale'; +import dayjs from 'dayjs'; +import { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { ExplorePageState } from 'src/explore/types'; +import 'dayjs/locale/en'; +import 'dayjs/locale/fr'; +import 'dayjs/locale/es'; +import 'dayjs/locale/it'; +import 'dayjs/locale/zh-cn'; +import 'dayjs/locale/ja'; +import 'dayjs/locale/de'; +import 'dayjs/locale/pt'; +import 'dayjs/locale/pt-br'; +import 'dayjs/locale/ru'; +import 'dayjs/locale/ko'; +import 'dayjs/locale/sk'; +import 'dayjs/locale/sl'; +import 'dayjs/locale/nl'; + +export const LOCALE_MAPPING = { + en: () => import('antd-v5/locale/en_US'), + fr: () => import('antd-v5/locale/fr_FR'), + es: () => import('antd-v5/locale/es_ES'), + it: () => import('antd-v5/locale/it_IT'), + zh: () => import('antd-v5/locale/zh_CN'), + ja: () => import('antd-v5/locale/ja_JP'), + de: () => import('antd-v5/locale/de_DE'), + pt: () => import('antd-v5/locale/pt_PT'), + pt_BR: () => import('antd-v5/locale/pt_BR'), + ru: () => import('antd-v5/locale/ru_RU'), + ko: () => import('antd-v5/locale/ko_KR'), + sk: () => import('antd-v5/locale/sk_SK'), + sl: () => import('antd-v5/locale/sl_SI'), + nl: () => import('antd-v5/locale/nl_NL'), +}; + +export const useLocale = (): Locale | undefined | null => { + const [datePickerLocale, setDatePickerLocale] = useState< + Locale | undefined | null + >(null); + + // Retrieve the locale from Redux store + const localFromFlaskBabel = useSelector( + (state: ExplorePageState) => state?.common?.locale, + ); + + useEffect(() => { + if (datePickerLocale === null) { + if (localFromFlaskBabel && LOCALE_MAPPING[localFromFlaskBabel]) { + LOCALE_MAPPING[localFromFlaskBabel]() + .then((locale: { default: Locale }) => { + setDatePickerLocale(locale.default); + dayjs.locale(localFromFlaskBabel); + }) + .catch(() => setDatePickerLocale(undefined)); + } else { + setDatePickerLocale(undefined); + } + } + }, [datePickerLocale, localFromFlaskBabel]); + + return datePickerLocale; +}; diff --git a/superset-frontend/src/pages/ChartList/ChartList.test.jsx b/superset-frontend/src/pages/ChartList/ChartList.test.jsx index 7b3c354b3dce8..63e0c4f9df874 100644 --- a/superset-frontend/src/pages/ChartList/ChartList.test.jsx +++ b/superset-frontend/src/pages/ChartList/ChartList.test.jsx @@ -53,7 +53,7 @@ const mockCharts = [...new Array(3)].map((_, i) => ({ id: i, slice_name: `cool chart ${i}`, url: 'url', - viz_type: uiCore.VizType.LegacyBar, + viz_type: uiCore.VizType.Bar, datasource_name: `ds${i}`, thumbnail_url: '/thumbnail', })); diff --git a/superset-frontend/src/pages/DashboardList/index.tsx b/superset-frontend/src/pages/DashboardList/index.tsx index 361168f4188e3..93d2904086d53 100644 --- a/superset-frontend/src/pages/DashboardList/index.tsx +++ b/superset-frontend/src/pages/DashboardList/index.tsx @@ -34,6 +34,7 @@ import { } from 'src/views/CRUD/utils'; import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; +import { PublishedLabel } from 'src/components/Label'; import { TagsList } from 'src/components/Tags'; import handleResourceExport from 'src/utils/export'; import Loading from 'src/components/Loading'; @@ -343,8 +344,9 @@ function DashboardList(props: DashboardListProps) { row: { original: { status }, }, - }: any) => - status === DashboardStatus.PUBLISHED ? t('Published') : t('Draft'), + }: any) => ( + + ), Header: t('Status'), accessor: 'published', size: 'xl', diff --git a/superset-frontend/src/pages/DatasetList/index.tsx b/superset-frontend/src/pages/DatasetList/index.tsx index d90f60c59241a..8173456e97a98 100644 --- a/superset-frontend/src/pages/DatasetList/index.tsx +++ b/superset-frontend/src/pages/DatasetList/index.tsx @@ -41,6 +41,7 @@ import ListView, { Filters, FilterOperator, } from 'src/components/ListView'; +import { DatasetTypeLabel } from 'src/components/Label'; import Loading from 'src/components/Loading'; import SubMenu, { SubMenuProps, ButtonProps } from 'src/features/home/SubMenu'; import Owner from 'src/types/Owner'; @@ -93,8 +94,7 @@ const Actions = styled.div` } } color: ${({ theme }) => theme.colors.grayscale.light1}; - .ant-menu-item:hover { - color: ${({ theme }) => theme.colors.grayscale.light1}; + .antd5-menu-item:hover { cursor: default; } &::after { @@ -279,24 +279,7 @@ const DatasetList: FunctionComponent = ({ row: { original: { kind }, }, - }: any) => { - if (kind === 'physical') { - return ( - - - - ); - } - - return ( - - - - ); - }, + }: any) => null, accessor: 'kind_icon', disableSortBy: true, size: 'xs', @@ -360,7 +343,7 @@ const DatasetList: FunctionComponent = ({ row: { original: { kind }, }, - }: any) => (kind === 'physical' ? t('Physical') : t('Virtual')), + }: any) => , Header: t('Type'), accessor: 'kind', disableSortBy: true, diff --git a/superset-frontend/src/pages/Home/index.tsx b/superset-frontend/src/pages/Home/index.tsx index df382843a7014..90466fe96e7cd 100644 --- a/superset-frontend/src/pages/Home/index.tsx +++ b/superset-frontend/src/pages/Home/index.tsx @@ -94,9 +94,6 @@ const WelcomeContainer = styled.div` margin: 0px 2px; } } - .ant-menu.ant-menu-light.ant-menu-root.ant-menu-horizontal { - padding-left: ${({ theme }) => theme.gridUnit * 8}px; - } button { padding: 3px 21px; } diff --git a/superset-frontend/src/pages/QueryHistoryList/index.tsx b/superset-frontend/src/pages/QueryHistoryList/index.tsx index 05263b1207d16..a5967118bbbaa 100644 --- a/superset-frontend/src/pages/QueryHistoryList/index.tsx +++ b/superset-frontend/src/pages/QueryHistoryList/index.tsx @@ -214,8 +214,8 @@ function QueryList({ addDangerToast }: QueryListProps) { original: { start_time }, }, }: any) => { - const startMoment = extendedDayjs.utc(start_time).local(); - const formattedStartTimeData = startMoment + const start = extendedDayjs.utc(start_time).local(); + const formattedStartTimeData = start .format(DATETIME_WITH_TIME_ZONE) .split(' '); diff --git a/superset-frontend/src/reduxUtils.ts b/superset-frontend/src/reduxUtils.ts index 20153cb0d3c21..ea87285cb8e6f 100644 --- a/superset-frontend/src/reduxUtils.ts +++ b/superset-frontend/src/reduxUtils.ts @@ -19,14 +19,7 @@ import { nanoid } from 'nanoid'; import { compose } from 'redux'; import persistState, { StorageAdapter } from 'redux-localstorage'; -import { - isEqual, - omitBy, - omit, - isUndefined, - isNull, - isEqualWith, -} from 'lodash'; +import { isEqual, omitBy, omit, isEqualWith } from 'lodash'; import { ensureIsArray } from '@superset-ui/core'; export function addToObject( @@ -195,12 +188,12 @@ export function areObjectsEqual( let comp1 = obj1; let comp2 = obj2; if (opts.ignoreUndefined) { - comp1 = omitBy(comp1, isUndefined); - comp2 = omitBy(comp2, isUndefined); + comp1 = omitBy(comp1, i => i === undefined); + comp2 = omitBy(comp2, i => i === undefined); } if (opts.ignoreNull) { - comp1 = omitBy(comp1, isNull); - comp2 = omitBy(comp2, isNull); + comp1 = omitBy(comp1, i => i === null); + comp2 = omitBy(comp2, i => i === null); } if (opts.ignoreFields?.length) { const ignoreFields = ensureIsArray(opts.ignoreFields); diff --git a/superset-frontend/src/theme/index.ts b/superset-frontend/src/theme/index.ts index 1b067aa1a00f5..9cc2653c805cb 100644 --- a/superset-frontend/src/theme/index.ts +++ b/superset-frontend/src/theme/index.ts @@ -17,8 +17,10 @@ * under the License. */ +import { addAlpha } from '@superset-ui/core'; import { type ThemeConfig } from 'antd-v5'; import { theme as supersetTheme } from 'src/preamble'; +import { mix } from 'polished'; import { lightAlgorithm } from './light'; export enum ThemeType { @@ -72,14 +74,62 @@ const baseConfig: ThemeConfig = { Badge: { paddingXS: supersetTheme.gridUnit * 2, }, + Button: { + defaultBg: supersetTheme.colors.primary.light4, + defaultHoverBg: mix( + 0.1, + supersetTheme.colors.primary.base, + supersetTheme.colors.primary.light4, + ), + defaultActiveBg: mix( + 0.25, + supersetTheme.colors.primary.base, + supersetTheme.colors.primary.light4, + ), + defaultColor: supersetTheme.colors.primary.dark1, + defaultHoverColor: supersetTheme.colors.primary.dark1, + defaultBorderColor: 'transparent', + defaultHoverBorderColor: 'transparent', + colorPrimaryHover: supersetTheme.colors.primary.dark1, + colorPrimaryActive: mix( + 0.2, + supersetTheme.colors.grayscale.dark2, + supersetTheme.colors.primary.dark1, + ), + primaryColor: supersetTheme.colors.grayscale.light5, + colorPrimaryTextHover: supersetTheme.colors.grayscale.light5, + colorError: supersetTheme.colors.error.base, + colorErrorHover: mix( + 0.1, + supersetTheme.colors.grayscale.light5, + supersetTheme.colors.error.base, + ), + colorErrorBg: mix( + 0.2, + supersetTheme.colors.grayscale.dark2, + supersetTheme.colors.error.base, + ), + dangerColor: supersetTheme.colors.grayscale.light5, + colorLinkHover: supersetTheme.colors.primary.base, + linkHoverBg: 'transparent', + }, Card: { paddingLG: supersetTheme.gridUnit * 6, fontWeightStrong: supersetTheme.typography.weights.medium, colorBgContainer: supersetTheme.colors.grayscale.light4, }, + DatePicker: { + colorBgContainer: supersetTheme.colors.grayscale.light5, + colorBgElevated: supersetTheme.colors.grayscale.light5, + borderRadiusSM: supersetTheme.gridUnit / 2, + }, Divider: { colorSplit: supersetTheme.colors.grayscale.light3, }, + Dropdown: { + colorBgElevated: supersetTheme.colors.grayscale.light5, + zIndexPopup: supersetTheme.zIndex.max, + }, Input: { colorBorder: supersetTheme.colors.secondary.light3, colorBgContainer: supersetTheme.colors.grayscale.light5, @@ -100,6 +150,19 @@ const baseConfig: ThemeConfig = { colorSplit: supersetTheme.colors.grayscale.light3, colorText: supersetTheme.colors.grayscale.dark1, }, + Menu: { + itemHeight: 32, + colorBgContainer: supersetTheme.colors.grayscale.light5, + subMenuItemBg: supersetTheme.colors.grayscale.light5, + colorBgElevated: supersetTheme.colors.grayscale.light5, + boxShadowSecondary: `0 3px 6px -4px ${addAlpha(supersetTheme.colors.grayscale.dark2, 0.12)}, 0 6px 16px 0 ${addAlpha(supersetTheme.colors.grayscale.dark2, 0.08)}, 0 9px 28px 8px ${addAlpha(supersetTheme.colors.grayscale.dark2, 0.05)}`, + activeBarHeight: 0, + itemHoverBg: supersetTheme.colors.secondary.light5, + padding: supersetTheme.gridUnit * 2, + subMenuItemBorderRadius: 0, + horizontalLineHeight: 1.4, + zIndexPopup: supersetTheme.zIndex.max, + }, Modal: { colorBgMask: `${supersetTheme.colors.grayscale.dark2}73`, contentBg: supersetTheme.colors.grayscale.light5, diff --git a/superset-frontend/src/utils/common.js b/superset-frontend/src/utils/common.js index e9418f9d31d2d..4ceceb0b41200 100644 --- a/superset-frontend/src/utils/common.js +++ b/superset-frontend/src/utils/common.js @@ -30,7 +30,7 @@ export const NULL_STRING = ''; export const TRUE_STRING = 'TRUE'; export const FALSE_STRING = 'FALSE'; -// moment time format strings +// dayjs time format strings export const SHORT_DATE = 'MMM D, YYYY'; export const SHORT_TIME = 'h:m a'; diff --git a/superset-frontend/src/utils/dates.ts b/superset-frontend/src/utils/dates.ts index 5cf0cb435a08d..a1155dc1af909 100644 --- a/superset-frontend/src/utils/dates.ts +++ b/superset-frontend/src/utils/dates.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import dayjs, { Dayjs } from 'dayjs'; import utc from 'dayjs/plugin/utc'; import timezone from 'dayjs/plugin/timezone'; @@ -24,6 +25,7 @@ import relativeTime from 'dayjs/plugin/relativeTime'; import customParseFormat from 'dayjs/plugin/customParseFormat'; import duration from 'dayjs/plugin/duration'; import updateLocale from 'dayjs/plugin/updateLocale'; +import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'; dayjs.extend(utc); dayjs.extend(timezone); @@ -32,6 +34,11 @@ dayjs.extend(relativeTime); dayjs.extend(customParseFormat); dayjs.extend(duration); dayjs.extend(updateLocale); +dayjs.extend(isSameOrBefore); + +dayjs.updateLocale('en', { + invalidDate: 'Invalid date', +}); export const extendedDayjs = dayjs; diff --git a/superset-frontend/src/views/menu.tsx b/superset-frontend/src/views/menu.tsx index 4d7d40356c7aa..128fa9ca876bf 100644 --- a/superset-frontend/src/views/menu.tsx +++ b/superset-frontend/src/views/menu.tsx @@ -28,6 +28,7 @@ import createCache from '@emotion/cache'; import { ThemeProvider } from '@superset-ui/core'; import Menu from 'src/features/home/Menu'; import { theme } from 'src/preamble'; +import { AntdThemeProvider } from 'src/components/AntdThemeProvider'; import getBootstrapData from 'src/utils/getBootstrapData'; import { setupStore } from './store'; @@ -45,16 +46,18 @@ const app = ( // @ts-ignore: emotion types defs are incompatible between core and cache - - - - - - - + + + + + + + + + ); diff --git a/superset-frontend/src/visualizations/presets/MainPreset.js b/superset-frontend/src/visualizations/presets/MainPreset.js index 5dee38e4571aa..b79de8f26234a 100644 --- a/superset-frontend/src/visualizations/presets/MainPreset.js +++ b/superset-frontend/src/visualizations/presets/MainPreset.js @@ -25,27 +25,19 @@ import { import CalendarChartPlugin from '@superset-ui/legacy-plugin-chart-calendar'; import ChordChartPlugin from '@superset-ui/legacy-plugin-chart-chord'; import CountryMapChartPlugin from '@superset-ui/legacy-plugin-chart-country-map'; -import EventFlowChartPlugin from '@superset-ui/legacy-plugin-chart-event-flow'; -import HeatmapChartPlugin from '@superset-ui/legacy-plugin-chart-heatmap'; -import HistogramChartPlugin from '@superset-ui/legacy-plugin-chart-histogram'; import HorizonChartPlugin from '@superset-ui/legacy-plugin-chart-horizon'; import MapBoxChartPlugin from '@superset-ui/legacy-plugin-chart-map-box'; import PairedTTestChartPlugin from '@superset-ui/legacy-plugin-chart-paired-t-test'; import ParallelCoordinatesChartPlugin from '@superset-ui/legacy-plugin-chart-parallel-coordinates'; import PartitionChartPlugin from '@superset-ui/legacy-plugin-chart-partition'; import RoseChartPlugin from '@superset-ui/legacy-plugin-chart-rose'; -import SankeyChartPlugin from '@superset-ui/legacy-plugin-chart-sankey'; import TableChartPlugin from '@superset-ui/plugin-chart-table'; import { WordCloudChartPlugin } from '@superset-ui/plugin-chart-word-cloud'; import WorldMapChartPlugin from '@superset-ui/legacy-plugin-chart-world-map'; import { - AreaChartPlugin, - BarChartPlugin, BubbleChartPlugin, BulletChartPlugin, CompareChartPlugin, - DistBarChartPlugin, - LineChartPlugin, TimePivotChartPlugin, } from '@superset-ui/legacy-preset-chart-nvd3'; import { DeckGLChartPreset } from '@superset-ui/legacy-preset-chart-deckgl'; @@ -105,8 +97,6 @@ export default class MainPreset extends Preset { name: 'Legacy charts', presets: [new DeckGLChartPreset()], plugins: [ - new AreaChartPlugin().configure({ key: VizType.LegacyArea }), - new BarChartPlugin().configure({ key: VizType.LegacyBar }), new BigNumberChartPlugin().configure({ key: VizType.BigNumber }), new BigNumberTotalChartPlugin().configure({ key: VizType.BigNumberTotal, @@ -118,8 +108,6 @@ export default class MainPreset extends Preset { new ChordChartPlugin().configure({ key: VizType.Chord }), new CompareChartPlugin().configure({ key: VizType.Compare }), new CountryMapChartPlugin().configure({ key: VizType.CountryMap }), - new DistBarChartPlugin().configure({ key: VizType.DistBar }), - new EventFlowChartPlugin().configure({ key: VizType.EventFlow }), new EchartsFunnelChartPlugin().configure({ key: VizType.Funnel }), new EchartsSankeyChartPlugin().configure({ key: VizType.Sankey }), new EchartsTreemapChartPlugin().configure({ key: VizType.Treemap }), @@ -129,10 +117,7 @@ export default class MainPreset extends Preset { new EchartsMixedTimeseriesChartPlugin().configure({ key: VizType.MixedTimeseries, }), - new HeatmapChartPlugin().configure({ key: VizType.LegacyHeatmap }), - new HistogramChartPlugin().configure({ key: VizType.LegacyHistogram }), new HorizonChartPlugin().configure({ key: VizType.Horizon }), - new LineChartPlugin().configure({ key: VizType.LegacyLine }), new MapBoxChartPlugin().configure({ key: VizType.MapBox }), new PairedTTestChartPlugin().configure({ key: VizType.PairedTTest }), new ParallelCoordinatesChartPlugin().configure({ @@ -142,7 +127,6 @@ export default class MainPreset extends Preset { new EchartsPieChartPlugin().configure({ key: VizType.Pie }), new PivotTableChartPluginV2().configure({ key: VizType.PivotTable }), new RoseChartPlugin().configure({ key: VizType.Rose }), - new SankeyChartPlugin().configure({ key: VizType.LegacySankey }), new TableChartPlugin().configure({ key: VizType.Table }), new TimePivotChartPlugin().configure({ key: VizType.TimePivot }), new TimeTableChartPlugin().configure({ key: VizType.TimeTable }), diff --git a/superset-frontend/webpack.config.js b/superset-frontend/webpack.config.js index dcc79a900f79d..1bf1f72b01205 100644 --- a/superset-frontend/webpack.config.js +++ b/superset-frontend/webpack.config.js @@ -24,7 +24,6 @@ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); const CopyPlugin = require('copy-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const MomentLocalesPlugin = require('moment-locales-webpack-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); const { @@ -42,24 +41,6 @@ const APP_DIR = path.resolve(__dirname, './'); // output dir const BUILD_DIR = path.resolve(__dirname, '../superset/static/assets'); const ROOT_DIR = path.resolve(__dirname, '..'); -const TRANSLATIONS_DIR = path.resolve(__dirname, '../superset/translations'); - -const getAvailableTranslationCodes = () => { - if (process.env.BUILD_TRANSLATIONS === 'true') { - const LOCALE_CODE_MAPPING = { - zh: 'zh-cn', - }; - const files = fs.readdirSync(TRANSLATIONS_DIR); - return files - .filter(file => - fs.statSync(path.join(TRANSLATIONS_DIR, file)).isDirectory(), - ) - .filter(dirName => !dirName.startsWith('__')) - .map(dirName => dirName.replace('_', '-')) - .map(dirName => LOCALE_CODE_MAPPING[dirName] || dirName); - } - return []; -}; const { mode = 'development', @@ -159,9 +140,6 @@ const plugins = [ chunks: [], filename: '500.html', }), - new MomentLocalesPlugin({ - localesToKeep: getAvailableTranslationCodes(), - }), ]; if (!process.env.CI) { @@ -253,6 +231,9 @@ const config = { message: /export 'withTooltipPropTypes' \(imported as 'vxTooltipPropTypes'\) was not found/, }, + { + message: /Can't resolve.*superset_text/, + }, ], performance: { assetFilter(assetFilename) { @@ -540,26 +521,24 @@ Object.entries(packageConfig.dependencies).forEach(([pkg, relativeDir]) => { }); console.log(''); // pure cosmetic new line -let proxyConfig = getProxyConfig(); - if (isDevMode) { - config.devServer = { - onBeforeSetupMiddleware(devServer) { - // load proxy config when manifest updates - const { afterEmit } = getCompilerHooks(devServer.compiler); + let proxyConfig = getProxyConfig(); + // Set up a plugin to handle manifest updates + config.plugins = config.plugins || []; + config.plugins.push({ + apply: compiler => { + const { afterEmit } = getCompilerHooks(compiler); afterEmit.tap('ManifestPlugin', manifest => { proxyConfig = getProxyConfig(manifest); }); }, + }); + + config.devServer = { historyApiFallback: true, hot: true, port: devserverPort, - // Only serves bundled files from webpack-dev-server - // and proxy everything else to Superset backend - proxy: [ - // functions are called for every request - () => proxyConfig, - ], + proxy: [() => proxyConfig], client: { overlay: { errors: true, @@ -568,7 +547,9 @@ if (isDevMode) { }, logging: 'error', }, - static: path.join(process.cwd(), '../static/assets'), + static: { + directory: path.join(process.cwd(), '../static/assets'), + }, }; } diff --git a/superset-frontend/webpack.proxy-config.js b/superset-frontend/webpack.proxy-config.js index c2070bb92b67d..41835d88faf06 100644 --- a/superset-frontend/webpack.proxy-config.js +++ b/superset-frontend/webpack.proxy-config.js @@ -24,12 +24,12 @@ const yargs = require('yargs'); const parsedArgs = yargs.argv; const parsedEnvArg = () => { + let envArgs = {}; if (parsedArgs.env) { - return yargs(parsedArgs.env).argv; + envArgs = yargs(parsedArgs.env).argv; } - return {}; + return { ...process.env, ...envArgs }; }; - const { supersetPort = 8088, superset: supersetUrl = null } = parsedEnvArg(); const backend = (supersetUrl || `http://localhost:${supersetPort}`).replace( '//+$/', diff --git a/superset-websocket/README.md b/superset-websocket/README.md index bfaf940b1111a..79cbe8139725b 100644 --- a/superset-websocket/README.md +++ b/superset-websocket/README.md @@ -91,7 +91,7 @@ Note also that `localhost` and `127.0.0.1` are not considered the same host. For The following config values must contain the same values in both the Flask app config and `config.json`: ```text -GLOBAL_ASYNC_QUERIES_REDIS_CONFIG +GLOBAL_ASYNC_QUERIES_CACHE_BACKEND GLOBAL_ASYNC_QUERIES_REDIS_STREAM_PREFIX GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME GLOBAL_ASYNC_QUERIES_JWT_SECRET diff --git a/superset/async_events/async_query_manager.py b/superset/async_events/async_query_manager.py index 0e59b2c3ddfd5..d955cc8827876 100644 --- a/superset/async_events/async_query_manager.py +++ b/superset/async_events/async_query_manager.py @@ -18,10 +18,9 @@ import logging import uuid -from typing import Any, Literal, Optional, Union +from typing import Any, Literal, Optional import jwt -import redis from flask import Flask, Request, request, Response, session from flask_caching.backends.base import BaseCache @@ -43,6 +42,10 @@ class AsyncQueryTokenException(Exception): # noqa: N818 pass +class UnsupportedCacheBackendError(Exception): # noqa: N818 + pass + + class AsyncQueryJobException(Exception): # noqa: N818 pass @@ -77,7 +80,7 @@ def increment_id(entry_id: str) -> str: def get_cache_backend( config: dict[str, Any], -) -> Union[RedisCacheBackend, RedisSentinelCacheBackend, redis.Redis]: # type: ignore +) -> RedisCacheBackend | RedisSentinelCacheBackend: cache_config = config.get("GLOBAL_ASYNC_QUERIES_CACHE_BACKEND", {}) cache_type = cache_config.get("CACHE_TYPE") @@ -87,11 +90,8 @@ def get_cache_backend( if cache_type == "RedisSentinelCache": return RedisSentinelCacheBackend.from_config(cache_config) - # TODO: Deprecate hardcoded plain Redis code and expand cache backend options. - # Maintain backward compatibility with 'GLOBAL_ASYNC_QUERIES_REDIS_CONFIG' until it is deprecated. # noqa: E501 - return redis.Redis( - **config["GLOBAL_ASYNC_QUERIES_REDIS_CONFIG"], decode_responses=True - ) + # TODO: Expand cache backend options. + raise UnsupportedCacheBackendError("Unsupported cache backend configuration") class AsyncQueryManager: diff --git a/superset/cli/examples.py b/superset/cli/examples.py index 51b89f9641000..9cd81a5be70fa 100755 --- a/superset/cli/examples.py +++ b/superset/cli/examples.py @@ -32,10 +32,10 @@ def load_examples_run( force: bool = False, ) -> None: if only_metadata: - print("Loading examples metadata") + logger.info("Loading examples metadata") else: examples_db = database_utils.get_example_database() - print(f"Loading examples metadata and related data into {examples_db}") + logger.info(f"Loading examples metadata and related data into {examples_db}") # pylint: disable=import-outside-toplevel import superset.examples.data_loading as examples @@ -43,45 +43,45 @@ def load_examples_run( examples.load_css_templates() if load_test_data: - print("Loading energy related dataset") + logger.info("Loading energy related dataset") examples.load_energy(only_metadata, force) - print("Loading [World Bank's Health Nutrition and Population Stats]") + logger.info("Loading [World Bank's Health Nutrition and Population Stats]") examples.load_world_bank_health_n_pop(only_metadata, force) - print("Loading [Birth names]") + logger.info("Loading [Birth names]") examples.load_birth_names(only_metadata, force) if load_test_data: - print("Loading [Tabbed dashboard]") + logger.info("Loading [Tabbed dashboard]") examples.load_tabbed_dashboard(only_metadata) - print("Loading [Supported Charts Dashboard]") + logger.info("Loading [Supported Charts Dashboard]") examples.load_supported_charts_dashboard() else: - print("Loading [Random long/lat data]") + logger.info("Loading [Random long/lat data]") examples.load_long_lat_data(only_metadata, force) - print("Loading [Country Map data]") + logger.info("Loading [Country Map data]") examples.load_country_map_data(only_metadata, force) - print("Loading [San Francisco population polygons]") + logger.info("Loading [San Francisco population polygons]") examples.load_sf_population_polygons(only_metadata, force) - print("Loading [Flights data]") + logger.info("Loading [Flights data]") examples.load_flights(only_metadata, force) - print("Loading [BART lines]") + logger.info("Loading [BART lines]") examples.load_bart_lines(only_metadata, force) - print("Loading [Misc Charts] dashboard") + logger.info("Loading [Misc Charts] dashboard") examples.load_misc_dashboard() - print("Loading DECK.gl demo") + logger.info("Loading DECK.gl demo") examples.load_deck_dash() if load_big_data: - print("Loading big synthetic data for tests") + logger.info("Loading big synthetic data for tests") examples.load_big_data() # load examples that are stored as YAML config files diff --git a/superset/commands/dashboard/export.py b/superset/commands/dashboard/export.py index 93cc490ad73de..719aed6be5191 100644 --- a/superset/commands/dashboard/export.py +++ b/superset/commands/dashboard/export.py @@ -83,7 +83,7 @@ def append_charts(position: dict[str, Any], charts: set[Slice]) -> dict[str, Any "parents": ["ROOT_ID", "GRID_ID"], } - for chart_hash, chart in zip(chart_hashes, charts): + for chart_hash, chart in zip(chart_hashes, charts, strict=False): position[chart_hash] = { "children": [], "id": chart_hash, diff --git a/superset/commands/database/tables.py b/superset/commands/database/tables.py index b28d4f065f626..c8fa88400cc5e 100644 --- a/superset/commands/database/tables.py +++ b/superset/commands/database/tables.py @@ -32,6 +32,7 @@ from superset.exceptions import SupersetException from superset.extensions import db, security_manager from superset.models.core import Database +from superset.utils.core import DatasourceName logger = logging.getLogger(__name__) @@ -59,7 +60,8 @@ def run(self) -> dict[str, Any]: catalog=self._catalog_name, schema=self._schema_name, datasource_names=sorted( - self._model.get_all_table_names_in_schema( + DatasourceName(*datasource_name) + for datasource_name in self._model.get_all_table_names_in_schema( catalog=self._catalog_name, schema=self._schema_name, force=self._force, @@ -74,7 +76,8 @@ def run(self) -> dict[str, Any]: catalog=self._catalog_name, schema=self._schema_name, datasource_names=sorted( - self._model.get_all_view_names_in_schema( + DatasourceName(*datasource_name) + for datasource_name in self._model.get_all_view_names_in_schema( catalog=self._catalog_name, schema=self._schema_name, force=self._force, diff --git a/superset/commands/database/update.py b/superset/commands/database/update.py index 85439afa71969..fbf90694f48e6 100644 --- a/superset/commands/database/update.py +++ b/superset/commands/database/update.py @@ -44,6 +44,7 @@ from superset.db_engine_specs.base import GenericDBException from superset.exceptions import OAuth2RedirectError from superset.models.core import Database +from superset.utils import json from superset.utils.decorators import on_error, transaction logger = logging.getLogger(__name__) @@ -66,22 +67,23 @@ def run(self) -> Model: self.validate() - # unmask ``encrypted_extra`` - self._properties["encrypted_extra"] = ( - self._model.db_engine_spec.unmask_encrypted_extra( - self._model.encrypted_extra, - self._properties.pop("masked_encrypted_extra", "{}"), + if "masked_encrypted_extra" in self._properties: + # unmask ``encrypted_extra`` + self._properties["encrypted_extra"] = ( + self._model.db_engine_spec.unmask_encrypted_extra( + self._model.encrypted_extra, + self._properties.pop("masked_encrypted_extra"), + ) ) - ) + + # Depending on the changes to the OAuth2 configuration we may need to purge + # existing personal tokens. + self._handle_oauth2() # if the database name changed we need to update any existing permissions, # since they're name based original_database_name = self._model.database_name - # Depending on the changes to the OAuth2 configuration we may need to purge - # existing personal tokens. - self._handle_oauth2() - database = DatabaseDAO.update(self._model, self._properties) database.set_sqlalchemy_uri(database.sqlalchemy_uri) ssh_tunnel = self._handle_ssh_tunnel(database) @@ -99,11 +101,16 @@ def _handle_oauth2(self) -> None: if not self._model: return + if self._properties["encrypted_extra"] is None: + self._model.purge_oauth2_tokens() + return + current_config = self._model.get_oauth2_config() if not current_config: return - new_config = self._properties["encrypted_extra"].get("oauth2_client_info", {}) + encrypted_extra = json.loads(self._properties["encrypted_extra"]) + new_config = encrypted_extra.get("oauth2_client_info", {}) # Keys that require purging personal tokens because they probably are no longer # valid. For example, if the scope has changed the existing tokens are still diff --git a/superset/commands/report/alert.py b/superset/commands/report/alert.py index ea45853b29a5f..d713c45811021 100644 --- a/superset/commands/report/alert.py +++ b/superset/commands/report/alert.py @@ -20,6 +20,7 @@ from operator import eq, ge, gt, le, lt, ne from timeit import default_timer from typing import Any +from uuid import UUID import numpy as np import pandas as pd @@ -40,6 +41,7 @@ from superset.tasks.utils import get_executor from superset.utils import json from superset.utils.core import override_user +from superset.utils.decorators import logs_context from superset.utils.retries import retry_call logger = logging.getLogger(__name__) @@ -52,8 +54,9 @@ class AlertCommand(BaseCommand): - def __init__(self, report_schedule: ReportSchedule): + def __init__(self, report_schedule: ReportSchedule, execution_id: UUID): self._report_schedule = report_schedule + self._execution_id = execution_id self._result: float | None = None def run(self) -> bool: @@ -135,6 +138,13 @@ def _is_validator_operator(self) -> bool: self._report_schedule.validator_type == ReportScheduleValidatorType.OPERATOR ) + def _get_alert_metadata_from_object(self) -> dict[str, Any]: + return { + "report_schedule_id": self._report_schedule.id, + "execution_id": self._execution_id, + } + + @logs_context(context_func=_get_alert_metadata_from_object) def _execute_query(self) -> pd.DataFrame: """ Executes the actual alert SQL query template @@ -152,6 +162,13 @@ def _execute_query(self) -> pd.DataFrame: rendered_sql, ALERT_SQL_LIMIT ) + if app.config["MUTATE_ALERT_QUERY"]: + limited_rendered_sql = ( + self._report_schedule.database.mutate_sql_based_on_config( + limited_rendered_sql + ) + ) + executor, username = get_executor( # pylint: disable=unused-variable executor_types=app.config["ALERT_REPORTS_EXECUTE_AS"], model=self._report_schedule, diff --git a/superset/commands/report/execute.py b/superset/commands/report/execute.py index 9293e967aa504..54a2890a96f91 100644 --- a/superset/commands/report/execute.py +++ b/superset/commands/report/execute.py @@ -698,7 +698,7 @@ def next(self) -> None: try: # If it's an alert check if the alert is triggered if self._report_schedule.type == ReportScheduleType.ALERT: - if not AlertCommand(self._report_schedule).run(): + if not AlertCommand(self._report_schedule, self._execution_id).run(): self.update_report_schedule_and_log(ReportState.NOOP) return self.send() @@ -782,7 +782,7 @@ def next(self) -> None: return self.update_report_schedule_and_log(ReportState.WORKING) try: - if not AlertCommand(self._report_schedule).run(): + if not AlertCommand(self._report_schedule, self._execution_id).run(): self.update_report_schedule_and_log(ReportState.NOOP) return except Exception as ex: diff --git a/superset/commands/sql_lab/permalink/__init__.py b/superset/commands/sql_lab/permalink/__init__.py new file mode 100644 index 0000000000000..13a83393a9124 --- /dev/null +++ b/superset/commands/sql_lab/permalink/__init__.py @@ -0,0 +1,16 @@ +# 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. diff --git a/superset/commands/sql_lab/permalink/base.py b/superset/commands/sql_lab/permalink/base.py new file mode 100644 index 0000000000000..1619ddb91de30 --- /dev/null +++ b/superset/commands/sql_lab/permalink/base.py @@ -0,0 +1,35 @@ +# 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. +from abc import ABC + +from superset.commands.base import BaseCommand +from superset.key_value.shared_entries import get_permalink_salt +from superset.key_value.types import ( + KeyValueResource, + MarshmallowKeyValueCodec, + SharedKey, +) +from superset.sqllab.permalink.schemas import SqlLabPermalinkSchema + + +class BaseSqlLabPermalinkCommand(BaseCommand, ABC): + resource: KeyValueResource = KeyValueResource.SQLLAB_PERMALINK + codec = MarshmallowKeyValueCodec(SqlLabPermalinkSchema()) + + @property + def salt(self) -> str: + return get_permalink_salt(SharedKey.SQLLAB_PERMALINK_SALT) diff --git a/superset/commands/sql_lab/permalink/create.py b/superset/commands/sql_lab/permalink/create.py new file mode 100644 index 0000000000000..150b069d6f70a --- /dev/null +++ b/superset/commands/sql_lab/permalink/create.py @@ -0,0 +1,49 @@ +# 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 logging +from typing import Any + +from superset import db +from superset.commands.sql_lab.permalink.base import BaseSqlLabPermalinkCommand +from superset.daos.key_value import KeyValueDAO +from superset.key_value.exceptions import KeyValueCodecEncodeException +from superset.key_value.utils import encode_permalink_key +from superset.sqllab.permalink.exceptions import SqlLabPermalinkCreateFailedError + +logger = logging.getLogger(__name__) + + +class CreateSqlLabPermalinkCommand(BaseSqlLabPermalinkCommand): + def __init__(self, state: dict[str, Any]): + self._properties = state.copy() + + def run(self) -> str: + self.validate() + try: + entry = KeyValueDAO.create_entry( + self.resource, self._properties, self.codec + ) + db.session.flush() + key = entry.id + if key is None: + raise SqlLabPermalinkCreateFailedError("Unexpected missing key id") + return encode_permalink_key(key=key, salt=self.salt) + except KeyValueCodecEncodeException as ex: + raise SqlLabPermalinkCreateFailedError(str(ex)) from ex + + def validate(self) -> None: + pass diff --git a/superset/commands/sql_lab/permalink/get.py b/superset/commands/sql_lab/permalink/get.py new file mode 100644 index 0000000000000..b6786e0e4d5d5 --- /dev/null +++ b/superset/commands/sql_lab/permalink/get.py @@ -0,0 +1,71 @@ +# 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 logging +from typing import Optional + +from superset import db +from superset.commands.dataset.exceptions import DatasetNotFoundError +from superset.commands.sql_lab.permalink.base import BaseSqlLabPermalinkCommand +from superset.daos.key_value import KeyValueDAO +from superset.key_value.exceptions import ( + KeyValueCodecDecodeException, + KeyValueGetFailedError, + KeyValueParseKeyError, +) +from superset.key_value.utils import decode_permalink_id +from superset.models import core as models +from superset.sqllab.permalink.exceptions import SqlLabPermalinkGetFailedError +from superset.sqllab.permalink.types import SqlLabPermalinkValue +from superset.utils import core as utils, json + +logger = logging.getLogger(__name__) + + +class GetSqlLabPermalinkCommand(BaseSqlLabPermalinkCommand): + def __init__(self, key: str): + self.key = key + + def run(self) -> Optional[SqlLabPermalinkValue]: + self.validate() + if self.key.startswith("kv:"): + id = int(self.key[3]) + try: + kv = db.session.query(models.KeyValue).filter_by(id=id).scalar() + if not kv: + return None + return json.loads(kv.value) + except Exception as ex: + raise SqlLabPermalinkGetFailedError( + message=utils.error_msg_from_exception(ex) + ) from ex + + try: + key = decode_permalink_id(self.key, salt=self.salt) + value = KeyValueDAO.get_value(self.resource, key, self.codec) + if value: + return value + return None + except ( + DatasetNotFoundError, + KeyValueCodecDecodeException, + KeyValueGetFailedError, + KeyValueParseKeyError, + ) as ex: + raise SqlLabPermalinkGetFailedError(message=ex.message) from ex + + def validate(self) -> None: + pass diff --git a/superset/commands/tag/create.py b/superset/commands/tag/create.py index 775250dc8172d..b3788eb8702d4 100644 --- a/superset/commands/tag/create.py +++ b/superset/commands/tag/create.py @@ -88,27 +88,32 @@ def run(self) -> tuple[set[tuple[str, int]], set[tuple[str, int]]]: def validate(self) -> None: exceptions = [] objects_to_tag = set(self._properties.get("objects_to_tag", [])) + for obj_type, obj_id in objects_to_tag: object_type = to_object_type(obj_type) # Validate object type - for obj_type, obj_id in objects_to_tag: - object_type = to_object_type(obj_type) - - if not object_type: - exceptions.append( - TagInvalidError(f"invalid object type {object_type}") - ) - try: - if model := to_object_model(object_type, obj_id): # type: ignore - security_manager.raise_for_ownership(model) - except SupersetSecurityException: - # skip the object if the user doesn't have access - self._skipped_tagged_objects.add((obj_type, obj_id)) + if not object_type: + exceptions.append(TagInvalidError(f"invalid object type {object_type}")) + continue - self._properties["objects_to_tag"] = ( - set(objects_to_tag) - self._skipped_tagged_objects - ) + try: + if model := to_object_model(object_type, obj_id): + try: + security_manager.raise_for_ownership(model) + except SupersetSecurityException: + if ( + not model.created_by + or model.created_by != security_manager.current_user + ): + # skip the object if the user doesn't have access + self._skipped_tagged_objects.add((obj_type, obj_id)) + except Exception as e: + exceptions.append(TagInvalidError(str(e))) + + self._properties["objects_to_tag"] = ( + set(objects_to_tag) - self._skipped_tagged_objects + ) if exceptions: raise TagInvalidError(exceptions=exceptions) diff --git a/superset/config.py b/superset/config.py index 5d03016298aae..ae944f3ca11ef 100644 --- a/superset/config.py +++ b/superset/config.py @@ -58,7 +58,7 @@ from superset.superset_typing import CacheConfig from superset.tasks.types import ExecutorType from superset.utils import core as utils -from superset.utils.core import is_test, NO_TIME_RANGE, parse_boolean_string +from superset.utils.core import NO_TIME_RANGE, parse_boolean_string from superset.utils.encrypt import SQLAlchemyUtilsAdapter from superset.utils.log import DBEventLogger from superset.utils.logging_configurator import DefaultLoggingConfigurator @@ -74,6 +74,8 @@ from superset.models.dashboard import Dashboard from superset.models.slice import Slice + DialectExtensions = dict[str, Dialects | type[Dialect]] + # Realtime stats logger, a StatsD implementation exists STATS_LOGGER = DummyStatsLogger() @@ -251,7 +253,7 @@ def _try_json_readsha(filepath: str, length: int) -> str | None: ) # Extends the default SQLGlot dialects with additional dialects -SQLGLOT_DIALECTS_EXTENSIONS: dict[str, Dialects | type[Dialect]] = {} +SQLGLOT_DIALECTS_EXTENSIONS: DialectExtensions | Callable[[], DialectExtensions] = {} # The limit of queries fetched for query search QUERY_SEARCH_LIMIT = 1000 @@ -470,7 +472,6 @@ class D3TimeFormat(TypedDict, total=False): # geospatial ones) by inputting javascript in controls. This exposes # an XSS security vulnerability "ENABLE_JAVASCRIPT_CONTROLS": False, # deprecated - "KV_STORE": False, # deprecated # When this feature is enabled, nested types in Presto will be # expanded into extra columns and/or arrays. This is experimental, # and doesn't work with all nested types. @@ -485,7 +486,6 @@ class D3TimeFormat(TypedDict, total=False): # This feature flag is used by the download feature in the dashboard view. # It is dependent on ENABLE_DASHBOARD_SCREENSHOT_ENDPOINT being enabled. "ENABLE_DASHBOARD_DOWNLOAD_WEBDRIVER_SCREENSHOT": False, - "SHARE_QUERIES_VIA_KV_STORE": False, "TAGGING_SYSTEM": False, "SQLLAB_BACKEND_PERSISTENCE": True, "LISTVIEWS_DEFAULT_CARD_VIEW": False, @@ -513,10 +513,12 @@ class D3TimeFormat(TypedDict, total=False): # This could cause the server to run out of memory or compute. "ALLOW_FULL_CSV_EXPORT": False, "ALLOW_ADHOC_SUBQUERY": False, - "USE_ANALAGOUS_COLORS": False, + "USE_ANALOGOUS_COLORS": False, # Apply RLS rules to SQL Lab queries. This requires parsing and manipulating the # query, and might break queries and/or allow users to bypass RLS. Use with care! "RLS_IN_SQLLAB": False, + # Try to optimize SQL queries — for now only predicate pushdown is supported. + "OPTIMIZE_SQL": False, # When impersonating a user, use the email prefix instead of the username "IMPERSONATE_WITH_EMAIL_PREFIX": False, # Enable caching per impersonation key (e.g username) in a datasource where user @@ -750,17 +752,12 @@ class D3TimeFormat(TypedDict, total=False): # Image and file configuration # --------------------------------------------------- # The file upload folder, when using models with files -UPLOAD_FOLDER = BASE_DIR + "/app/static/uploads/" +UPLOAD_FOLDER = BASE_DIR + "/static/uploads/" UPLOAD_CHUNK_SIZE = 4096 -# The image upload folder, when using models with images -IMG_UPLOAD_FOLDER = BASE_DIR + "/app/static/uploads/" - -# The image upload url, when using models with images -IMG_UPLOAD_URL = "/static/uploads/" -# Setup image size default is (300, 200, True) -# IMG_SIZE = (300, 200, True) - +# --------------------------------------------------- +# Cache configuration +# --------------------------------------------------- # Default cache timeout, applies to all cache backends unless specifically overridden in # each cache config. CACHE_DEFAULT_TIMEOUT = int(timedelta(days=1).total_seconds()) @@ -830,10 +827,11 @@ class D3TimeFormat(TypedDict, total=False): # Chrome allows up to 6 open connections per domain at a time. When there are more # than 6 slices in dashboard, a lot of time fetch requests are queued up and wait for -# next available socket. PR #5039 is trying to allow domain sharding for Superset, -# and this feature will be enabled by configuration only (by default Superset -# doesn't allow cross-domain request). -SUPERSET_WEBSERVER_DOMAINS = None +# next available socket. PR #5039 added domain sharding for Superset, +# and this feature can be enabled by configuration only (by default Superset +# doesn't allow cross-domain request). This feature is deprecated, annd will be removed +# in the next major version of Superset, as enabling HTTP2 will serve the same goals. +SUPERSET_WEBSERVER_DOMAINS = None # deprecated # Allowed format types for upload on Database view EXCEL_EXTENSIONS = {"xlsx", "xls"} @@ -1378,6 +1376,10 @@ def SQL_QUERY_MUTATOR( # pylint: disable=invalid-name,unused-argument # noqa: MUTATE_AFTER_SPLIT = False +# Boolean config that determines if alert SQL queries should also be mutated or not. +MUTATE_ALERT_QUERY = False + + # This allows for a user to add header data to any outgoing emails. For example, # if you need to include metadata in the header or you want to change the specifications # of the email title, header, or sender. @@ -1486,7 +1488,10 @@ def EMAIL_HEADER_MUTATOR( # pylint: disable=invalid-name,unused-argument # noq WEBDRIVER_AUTH_FUNC = None # Any config options to be passed as-is to the webdriver -WEBDRIVER_CONFIGURATION: dict[Any, Any] = {"service_log_path": "/dev/null"} +WEBDRIVER_CONFIGURATION = { + "options": {"capabilities": {}, "preferences": {}}, + "service": {"log_output": "/dev/null", "service_args": [], "port": 0, "env": {}}, +} # Additional args to be passed as arguments to the config object # Note: If using Chrome, you'll want to add the "--marionette" arg. @@ -1714,13 +1719,6 @@ def EMAIL_HEADER_MUTATOR( # pylint: disable=invalid-name,unused-argument # noq GLOBAL_ASYNC_QUERY_MANAGER_CLASS = ( "superset.async_events.async_query_manager.AsyncQueryManager" ) -GLOBAL_ASYNC_QUERIES_REDIS_CONFIG = { - "port": 6379, - "host": "127.0.0.1", - "password": "", - "db": 0, - "ssl": False, -} GLOBAL_ASYNC_QUERIES_REDIS_STREAM_PREFIX = "async-events-" GLOBAL_ASYNC_QUERIES_REDIS_STREAM_LIMIT = 1000 GLOBAL_ASYNC_QUERIES_REDIS_STREAM_LIMIT_FIREHOSE = 1000000 @@ -1741,7 +1739,6 @@ def EMAIL_HEADER_MUTATOR( # pylint: disable=invalid-name,unused-argument # noq # Global async queries cache backend configuration options: # - Set 'CACHE_TYPE' to 'RedisCache' for RedisCacheBackend. # - Set 'CACHE_TYPE' to 'RedisSentinelCache' for RedisSentinelCacheBackend. -# - Set 'CACHE_TYPE' to 'None' to fall back on 'GLOBAL_ASYNC_QUERIES_REDIS_CONFIG'. GLOBAL_ASYNC_QUERIES_CACHE_BACKEND = { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_HOST": "localhost", @@ -1921,7 +1918,7 @@ class ExtraDynamicQueryFilters(TypedDict, total=False): "Failed to import config for %s=%s", CONFIG_PATH_ENV_VAR, cfg_path ) raise -elif importlib.util.find_spec("superset_config") and not is_test(): +elif importlib.util.find_spec("superset_config"): try: # pylint: disable=import-error,wildcard-import,unused-wildcard-import import superset_config diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py index ac3a75481c02f..2acc7b12b6647 100644 --- a/superset/connectors/sqla/models.py +++ b/superset/connectors/sqla/models.py @@ -1568,7 +1568,11 @@ def adhoc_column_to_sqla( # pylint: disable=too-many-locals # probe adhoc column type tbl, _ = self.get_from_clause(template_processor) qry = sa.select([sqla_column]).limit(1).select_from(tbl) - sql = self.database.compile_sqla_query(qry) + sql = self.database.compile_sqla_query( + qry, + catalog=self.catalog, + schema=self.schema, + ) col_desc = get_columns_description( self.database, self.catalog, @@ -1903,6 +1907,7 @@ def query_datasources_by_permissions( # pylint: disable=invalid-name for method, perms in zip( (SqlaTable.perm, SqlaTable.schema_perm, SqlaTable.catalog_perm), (permissions, schema_perms, catalog_perms), + strict=False, ) if perms ] diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py index d47c2ea3e8195..20f46b48e48bf 100644 --- a/superset/dashboards/api.py +++ b/superset/dashboards/api.py @@ -20,20 +20,19 @@ from datetime import datetime from http import HTTPStatus from io import BytesIO -from typing import Any, Callable, cast, Optional +from typing import Any, Callable, cast from zipfile import is_zipfile, ZipFile from flask import g, redirect, request, Response, send_file, url_for from flask_appbuilder import permission_name from flask_appbuilder.api import expose, protect, rison, safe -from flask_appbuilder.hooks import before_request from flask_appbuilder.models.sqla.interface import SQLAInterface from flask_babel import gettext, ngettext from marshmallow import ValidationError from werkzeug.wrappers import Response as WerkzeugResponse from werkzeug.wsgi import FileWrapper -from superset import db, is_feature_enabled, thumbnail_cache +from superset import db, thumbnail_cache from superset.charts.schemas import ChartEntityResponseSchema from superset.commands.dashboard.copy import CopyDashboardCommand from superset.commands.dashboard.create import CreateDashboardCommand @@ -125,6 +124,7 @@ requires_form_data, requires_json, statsd_metrics, + validate_feature_flags, ) from superset.views.error_handling import handle_api_exception from superset.views.filters import ( @@ -161,18 +161,6 @@ def wraps(self: BaseSupersetModelRestApi, id_or_slug: str) -> Response: class DashboardRestApi(BaseSupersetModelRestApi): datamodel = SQLAInterface(Dashboard) - @before_request(only=["thumbnail", "cache_dashboard_screenshot", "screenshot"]) - def ensure_thumbnails_enabled(self) -> Optional[Response]: - if not is_feature_enabled("THUMBNAILS"): - return self.response_404() - return None - - @before_request(only=["cache_dashboard_screenshot", "screenshot"]) - def ensure_screenshots_enabled(self) -> Optional[Response]: - if not is_feature_enabled("ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS"): - return self.response_404() - return None - include_route_methods = RouteMethod.REST_MODEL_VIEW_CRUD_SET | { RouteMethod.EXPORT, RouteMethod.IMPORT, @@ -206,9 +194,6 @@ def ensure_screenshots_enabled(self) -> Optional[Response]: "status", "slug", "url", - "css", - "position_json", - "json_metadata", "thumbnail_url", "certified_by", "certification_details", @@ -1042,6 +1027,7 @@ def export(self, **kwargs: Any) -> Response: return response @expose("//thumbnail//", methods=("GET",)) + @validate_feature_flags(["THUMBNAILS"]) @protect() @safe @rison(thumbnail_query_schema) @@ -1145,6 +1131,7 @@ def thumbnail(self, pk: int, digest: str, **kwargs: Any) -> WerkzeugResponse: ) @expose("//cache_dashboard_screenshot/", methods=("POST",)) + @validate_feature_flags(["THUMBNAILS", "ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS"]) @protect() @rison(screenshot_query_schema) @safe @@ -1245,6 +1232,7 @@ def trigger_celery() -> WerkzeugResponse: return trigger_celery() @expose("//screenshot//", methods=("GET",)) + @validate_feature_flags(["THUMBNAILS", "ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS"]) @protect() @safe @statsd_metrics diff --git a/superset/db_engine_specs/base.py b/superset/db_engine_specs/base.py index cd2c318ad86a8..f239ef2019266 100644 --- a/superset/db_engine_specs/base.py +++ b/superset/db_engine_specs/base.py @@ -1701,7 +1701,7 @@ def select_star( # pylint: disable=too-many-arguments ) if partition_query is not None: qry = partition_query - sql = database.compile_sqla_query(qry) + sql = database.compile_sqla_query(qry, table.catalog, table.schema) if indent: sql = SQLScript(sql, engine=cls.engine).format() return sql diff --git a/superset/db_engine_specs/bigquery.py b/superset/db_engine_specs/bigquery.py index 74a5d151f1e4f..ef0ce31b5818b 100644 --- a/superset/db_engine_specs/bigquery.py +++ b/superset/db_engine_specs/bigquery.py @@ -17,6 +17,7 @@ from __future__ import annotations +import logging import re import urllib from datetime import datetime @@ -50,6 +51,7 @@ from superset.utils.hashing import md5_sha_from_str try: + import google.auth from google.cloud import bigquery from google.oauth2 import service_account @@ -67,6 +69,9 @@ if TYPE_CHECKING: from superset.models.core import Database # pragma: no cover + +logger = logging.getLogger() + CONNECTION_DATABASE_PERMISSIONS_REGEX = re.compile( "Access Denied: Project (?P.+?): User does not have " + "bigquery.jobs.create permission in project (?P.+?)" @@ -422,10 +427,19 @@ def _get_client( "Could not import libraries needed to connect to BigQuery." ) - credentials = service_account.Credentials.from_service_account_info( - engine.dialect.credentials_info - ) - return bigquery.Client(credentials=credentials) + if credentials_info := engine.dialect.credentials_info: + credentials = service_account.Credentials.from_service_account_info( + credentials_info + ) + return bigquery.Client(credentials=credentials) + + try: + credentials = google.auth.default()[0] + return bigquery.Client(credentials=credentials) + except google.auth.exceptions.DefaultCredentialsError as ex: + raise SupersetDBAPIConnectionError( + "The database credentials could not be found." + ) from ex @classmethod def estimate_query_cost( # pylint: disable=too-many-arguments @@ -496,7 +510,17 @@ def get_catalog_names( """ engine: Engine with database.get_sqla_engine() as engine: - client = cls._get_client(engine, database) + try: + client = cls._get_client(engine, database) + except SupersetDBAPIConnectionError: + logger.warning( + "Could not connect to database to get catalogs due to missing " + "credentials. This is normal in certain circustances, for example, " + "doing an import." + ) + # return {} here, since it will be repopulated when creds are added + return set() + projects = client.list_projects() return {project.project_id for project in projects} diff --git a/superset/db_engine_specs/hive.py b/superset/db_engine_specs/hive.py index 9491ff5882eca..55e1187402a36 100644 --- a/superset/db_engine_specs/hive.py +++ b/superset/db_engine_specs/hive.py @@ -440,7 +440,7 @@ def where_latest_partition( # table is not partitioned return None if values is not None and columns is not None: - for col_name, value in zip(col_names, values): + for col_name, value in zip(col_names, values, strict=False): for clm in columns: if clm.get("name") == col_name: query = query.where(Column(col_name) == value) diff --git a/superset/db_engine_specs/ocient.py b/superset/db_engine_specs/ocient.py index e740ca938e254..a7b97ed699639 100644 --- a/superset/db_engine_specs/ocient.py +++ b/superset/db_engine_specs/ocient.py @@ -348,7 +348,9 @@ def identity(x: Any) -> Any: rows = [ tuple( sanitize_func(val) - for sanitize_func, val in zip(sanitization_functions, row) + for sanitize_func, val in zip( + sanitization_functions, row, strict=False + ) ) for row in rows ] diff --git a/superset/db_engine_specs/presto.py b/superset/db_engine_specs/presto.py index 3a3dadbbd5842..6f27503a2f285 100644 --- a/superset/db_engine_specs/presto.py +++ b/superset/db_engine_specs/presto.py @@ -545,7 +545,7 @@ def where_latest_partition( column.get("column_name"): column.get("type") for column in columns or [] } - for col_name, value in zip(col_names, values): + for col_name, value in zip(col_names, values, strict=False): col_type = column_type_by_name.get(col_name) if isinstance(col_type, str): @@ -1240,7 +1240,7 @@ def expand_data( # pylint: disable=too-many-locals # noqa: C901 if isinstance(values, str): values = cast(Optional[list[Any]], destringify(values)) row[name] = values - for value, col in zip(values or [], expanded): + for value, col in zip(values or [], expanded, strict=False): row[col["column_name"]] = value data = [ @@ -1271,7 +1271,7 @@ def get_extra_table_metadata( metadata["partitions"] = { "cols": sorted(indexes[0].get("column_names", [])), - "latest": dict(zip(col_names, latest_parts)), + "latest": dict(zip(col_names, latest_parts, strict=False)), "partitionQuery": cls._partition_query( table=table, indexes=indexes, diff --git a/superset/db_engine_specs/redshift.py b/superset/db_engine_specs/redshift.py index 8b5a35759b0c9..d1a09cf78bb69 100644 --- a/superset/db_engine_specs/redshift.py +++ b/superset/db_engine_specs/redshift.py @@ -131,7 +131,7 @@ def df_to_sql( # uses the max size for redshift nvarchar(65335) # the default object and string types create a varchar(256) col_name: NVARCHAR(length=65535) - for col_name, type in zip(df.columns, df.dtypes) + for col_name, type in zip(df.columns, df.dtypes, strict=False) if isinstance(type, pd.StringDtype) } diff --git a/superset/db_engine_specs/trino.py b/superset/db_engine_specs/trino.py index e4567082e44f6..c7ae2b0f82ebd 100644 --- a/superset/db_engine_specs/trino.py +++ b/superset/db_engine_specs/trino.py @@ -111,7 +111,7 @@ def get_extra_table_metadata( } ) ), - "latest": dict(zip(col_names, latest_parts)), + "latest": dict(zip(col_names, latest_parts, strict=False)), "partitionQuery": cls._partition_query( table=table, indexes=indexes, diff --git a/superset/examples/bart_lines.py b/superset/examples/bart_lines.py index c1a0897eb3f79..2005ace22b15e 100644 --- a/superset/examples/bart_lines.py +++ b/superset/examples/bart_lines.py @@ -14,6 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import logging import pandas as pd import polyline @@ -26,6 +27,8 @@ from ..utils.database import get_example_database from .helpers import get_example_url, get_table_connector_registry +logger = logging.getLogger(__name__) + def load_bart_lines(only_metadata: bool = False, force: bool = False) -> None: tbl_name = "bart_lines" @@ -56,7 +59,7 @@ def load_bart_lines(only_metadata: bool = False, force: bool = False) -> None: index=False, ) - print(f"Creating table {tbl_name} reference") + logger.debug(f"Creating table {tbl_name} reference") table = get_table_connector_registry() tbl = db.session.query(table).filter_by(table_name=tbl_name).first() if not tbl: diff --git a/superset/examples/big_data.py b/superset/examples/big_data.py index 0f3fd10fd91bb..738cae3d5702a 100644 --- a/superset/examples/big_data.py +++ b/superset/examples/big_data.py @@ -14,6 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import logging import random import string @@ -21,6 +22,8 @@ from superset.utils.mock_data import add_data, ColumnInfo +logger = logging.getLogger(__name__) + COLUMN_TYPES = [ sqlalchemy.sql.sqltypes.INTEGER(), sqlalchemy.sql.sqltypes.VARCHAR(length=255), @@ -34,7 +37,7 @@ def load_big_data() -> None: - print("Creating table `wide_table` with 100 columns") + logger.debug("Creating table `wide_table` with 100 columns") columns: list[ColumnInfo] = [] for i in range(100): column: ColumnInfo = { @@ -48,7 +51,7 @@ def load_big_data() -> None: columns.append(column) add_data(columns=columns, num_rows=1000, table_name="wide_table") - print("Creating 1000 small tables") + logger.debug("Creating 1000 small tables") columns = [ { "name": "id", @@ -70,6 +73,6 @@ def load_big_data() -> None: for i in range(1000): add_data(columns=columns, num_rows=10, table_name=f"small_table_{i}") - print("Creating table with long name") + logger.debug("Creating table with long name") name = "".join(random.choices(string.ascii_letters + string.digits, k=60)) # noqa: S311 add_data(columns=columns, num_rows=10, table_name=name) diff --git a/superset/examples/birth_names.py b/superset/examples/birth_names.py index 8d2b23e22f652..d94353219cfac 100644 --- a/superset/examples/birth_names.py +++ b/superset/examples/birth_names.py @@ -14,6 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import logging import textwrap from typing import Union @@ -40,6 +41,8 @@ update_slice_ids, ) +logger = logging.getLogger(__name__) + def gen_filter( subject: str, comparator: str, operator: str = "==" @@ -83,8 +86,8 @@ def load_data(tbl_name: str, database: Database, sample: bool = False) -> None: method="multi", index=False, ) - print("Done loading table!") - print("-" * 80) + logger.debug("Done loading table!") + logger.debug("-" * 80) def load_birth_names( @@ -104,7 +107,7 @@ def load_birth_names( table = get_table_connector_registry() obj = db.session.query(table).filter_by(table_name=tbl_name, schema=schema).first() if not obj: - print(f"Creating table [{tbl_name}] reference") + logger.debug(f"Creating table [{tbl_name}] reference") obj = table(table_name=tbl_name, schema=schema) db.session.add(obj) @@ -196,7 +199,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]: "datasource_type": DatasourceType.TABLE, } - print("Creating some slices") + logger.debug("Creating some slices") slices = [ Slice( **slice_kwargs, @@ -224,10 +227,10 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]: Slice( **slice_kwargs, slice_name="Trends", - viz_type="line", + viz_type="echarts_timeseries_line", params=get_slice_json( defaults, - viz_type="line", + viz_type="echarts_timeseries_line", groupby=["name"], granularity_sqla="ds", rich_tooltip=True, @@ -239,7 +242,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]: Slice( **slice_kwargs, slice_name="Genders by State", - viz_type="dist_bar", + viz_type="echarts_timeseries_bar", params=get_slice_json( defaults, adhoc_filters=[ @@ -252,7 +255,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]: "subject": "state", } ], - viz_type="dist_bar", + viz_type="echarts_timeseries_bar", metrics=[ { "expressionType": "SIMPLE", @@ -338,7 +341,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]: Slice( **slice_kwargs, slice_name="Top 10 Girl Name Share", - viz_type="area", + viz_type="echarts_area", params=get_slice_json( defaults, adhoc_filters=[gen_filter("gender", "girl")], @@ -347,7 +350,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]: limit=10, stacked_style="expand", time_grain_sqla="P1D", - viz_type="area", + viz_type="echarts_area", x_axis_forma="smart_date", metrics=metrics, ), @@ -356,7 +359,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]: Slice( **slice_kwargs, slice_name="Top 10 Boy Name Share", - viz_type="area", + viz_type="echarts_area", params=get_slice_json( defaults, adhoc_filters=[gen_filter("gender", "boy")], @@ -365,7 +368,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]: limit=10, stacked_style="expand", time_grain_sqla="P1D", - viz_type="area", + viz_type="echarts_area", x_axis_forma="smart_date", metrics=metrics, ), @@ -421,8 +424,10 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]: Slice( **slice_kwargs, slice_name="Num Births Trend", - viz_type="line", - params=get_slice_json(defaults, viz_type="line", metrics=metrics), + viz_type="echarts_timeseries_line", + params=get_slice_json( + defaults, viz_type="echarts_timeseries_line", metrics=metrics + ), owners=[], ), Slice( @@ -471,7 +476,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]: Slice( **slice_kwargs, slice_name="Top 10 California Names Timeseries", - viz_type="line", + viz_type="echarts_timeseries_line", params=get_slice_json( defaults, metrics=[ @@ -485,7 +490,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]: "label": "SUM(num_california)", } ], - viz_type="line", + viz_type="echarts_timeseries_line", granularity_sqla="ds", groupby=["name"], timeseries_limit_metric={ @@ -561,7 +566,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]: def create_dashboard(slices: list[Slice]) -> Dashboard: - print("Creating a dashboard") + logger.debug("Creating a dashboard") dash = db.session.query(Dashboard).filter_by(slug="births").first() if not dash: dash = Dashboard() @@ -579,7 +584,7 @@ def create_dashboard(slices: list[Slice]) -> Dashboard: } }""" ) - # pylint: disable=line-too-long + # pylint: disable=echarts_timeseries_line-too-long pos = json.loads( textwrap.dedent( """\ @@ -854,7 +859,7 @@ def create_dashboard(slices: list[Slice]) -> Dashboard: """ # noqa: E501 ) ) - # pylint: enable=line-too-long + # pylint: enable=echarts_timeseries_line-too-long # dashboard v2 doesn't allow add markup slice dash.slices = [slc for slc in slices if slc.viz_type != "markup"] update_slice_ids(pos) diff --git a/superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Approach__Stage.yaml b/superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Approach__Stage.yaml index ac789d3a94b9f..388a8504b9d30 100644 --- a/superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Approach__Stage.yaml +++ b/superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Approach__Stage.yaml @@ -15,34 +15,33 @@ # specific language governing permissions and limitations # under the License. slice_name: Vaccine Candidates per Approach & Stage -viz_type: heatmap +viz_type: heatmap_v2 params: adhoc_filters: [] - all_columns_x: clinical_stage - all_columns_y: product_category + x_axis: clinical_stage + groupby: product_category bottom_margin: auto - canvas_image_rendering: pixelated datasource: 69__table left_margin: auto linear_color_scheme: schemeYlOrBr metric: count - normalize_across: heatmap + normalize_across: heatmap_v2 queryFields: metric: metrics row_limit: 10000 show_legend: false - show_perc: true + show_percentage: true show_values: true slice_id: 3962 sort_x_axis: alpha_asc sort_y_axis: alpha_asc time_range: No filter url_params: {} - viz_type: heatmap + viz_type: heatmap_v2 xscale_interval: null - y_axis_bounds: - - null - - null + value_bounds: + - null + - null y_axis_format: SMART_NUMBER yscale_interval: null cache_timeout: null diff --git a/superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Country__Stage_749.yaml b/superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Country__Stage_749.yaml index 216231b3ec26c..13a761d9f6b08 100644 --- a/superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Country__Stage_749.yaml +++ b/superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Country__Stage_749.yaml @@ -15,30 +15,29 @@ # specific language governing permissions and limitations # under the License. slice_name: Vaccine Candidates per Country & Stage -viz_type: heatmap +viz_type: heatmap_v2 params: adhoc_filters: [] - all_columns_x: clinical_stage - all_columns_y: country_name + x_axis: clinical_stage + groupby: country_name bottom_margin: auto - canvas_image_rendering: pixelated datasource: 14__table left_margin: auto linear_color_scheme: schemeYlOrBr metric: count - normalize_across: heatmap + normalize_across: heatmap_v2 row_limit: 10000 show_legend: true - show_perc: true + show_percentage: true sort_x_axis: alpha_asc sort_y_axis: alpha_asc time_range: No filter url_params: {} - viz_type: heatmap + viz_type: heatmap_v2 xscale_interval: null - y_axis_bounds: - - null - - null + value_bounds: + - null + - null y_axis_format: SMART_NUMBER yscale_interval: null cache_timeout: null diff --git a/superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Phase_587.yaml b/superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Phase_587.yaml index 78070fce7cda7..906327ff66043 100644 --- a/superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Phase_587.yaml +++ b/superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Phase_587.yaml @@ -15,23 +15,22 @@ # specific language governing permissions and limitations # under the License. slice_name: Vaccine Candidates per Phase -viz_type: dist_bar +viz_type: echarts_timeseries_bar params: + x_axis: clinical_stage adhoc_filters: [] bottom_margin: auto color_scheme: SUPERSET_DEFAULT columns: [] datasource: 14__table - groupby: - - clinical_stage label_colors: {} metrics: - - count + - count row_limit: 10000 show_legend: false time_range: No filter url_params: {} - viz_type: dist_bar + viz_type: echarts_timeseries_bar x_ticks_layout: auto y_axis_format: SMART_NUMBER cache_timeout: null diff --git a/superset/examples/configs/charts/FCC New Coder Survey/Age_distribution_of_respondents.yaml b/superset/examples/configs/charts/FCC New Coder Survey/Age_distribution_of_respondents.yaml index 7b841611bffe2..e4da5c3d69627 100644 --- a/superset/examples/configs/charts/FCC New Coder Survey/Age_distribution_of_respondents.yaml +++ b/superset/examples/configs/charts/FCC New Coder Survey/Age_distribution_of_respondents.yaml @@ -15,24 +15,24 @@ # specific language governing permissions and limitations # under the License. slice_name: Age distribution of respondents -viz_type: histogram +viz_type: histogram_v2 params: adhoc_filters: [] all_columns_x: - - age + - age color_scheme: supersetColors datasource: 42__table granularity_sqla: time_start groupby: null label_colors: {} - link_length: '25' + link_length: "25" queryFields: groupby: groupby row_limit: 10000 slice_id: 1380 time_range: No filter url_params: {} - viz_type: histogram + viz_type: histogram_v2 x_axis_label: age y_axis_label: count cache_timeout: null diff --git a/superset/examples/configs/charts/FCC New Coder Survey/Ethnic_Minority__Gender.yaml b/superset/examples/configs/charts/FCC New Coder Survey/Ethnic_Minority__Gender.yaml index 5f9449fc308a4..59492c61466d0 100644 --- a/superset/examples/configs/charts/FCC New Coder Survey/Ethnic_Minority__Gender.yaml +++ b/superset/examples/configs/charts/FCC New Coder Survey/Ethnic_Minority__Gender.yaml @@ -15,33 +15,32 @@ # specific language governing permissions and limitations # under the License. slice_name: Ethnic Minority & Gender -viz_type: sankey +viz_type: sankey_v2 params: adhoc_filters: - - clause: WHERE - comparator: 'NULL' - expressionType: SIMPLE - filterOptionName: filter_of9xf5uks2_5pisp1se9r5 - isExtra: false - isNew: false - operator: '!=' - sqlExpression: null - subject: ethnic_minority - - clause: WHERE - comparator: 'NULL' - expressionType: SIMPLE - filterOptionName: filter_9ikn7htywfm_2579he7pk5x - isExtra: false - isNew: false - operator: '!=' - sqlExpression: null - subject: gender + - clause: WHERE + comparator: "NULL" + expressionType: SIMPLE + filterOptionName: filter_of9xf5uks2_5pisp1se9r5 + isExtra: false + isNew: false + operator: "!=" + sqlExpression: null + subject: ethnic_minority + - clause: WHERE + comparator: "NULL" + expressionType: SIMPLE + filterOptionName: filter_9ikn7htywfm_2579he7pk5x + isExtra: false + isNew: false + operator: "!=" + sqlExpression: null + subject: gender color_scheme: supersetColors datasource: 42__table granularity_sqla: time_start - groupby: - - ethnic_minority - - gender + source: ethnic_minority + target: gender label_colors: {} metric: count queryFields: @@ -50,7 +49,7 @@ params: row_limit: null time_range: No filter url_params: {} - viz_type: sankey + viz_type: sankey_v2 cache_timeout: null uuid: 4880e4f4-b701-4be0-86f3-e7e89432e83b version: 1.0.0 diff --git a/superset/examples/configs/charts/FCC New Coder Survey/First_Time_Developer__Commute_Time.yaml b/superset/examples/configs/charts/FCC New Coder Survey/First_Time_Developer__Commute_Time.yaml index b8c7ca54fa0a6..40852776f9b2d 100644 --- a/superset/examples/configs/charts/FCC New Coder Survey/First_Time_Developer__Commute_Time.yaml +++ b/superset/examples/configs/charts/FCC New Coder Survey/First_Time_Developer__Commute_Time.yaml @@ -15,42 +15,41 @@ # specific language governing permissions and limitations # under the License. slice_name: First Time Developer & Commute Time -viz_type: sankey +viz_type: sankey_v2 params: adhoc_filters: - - clause: WHERE - comparator: '1' - expressionType: SIMPLE - filterOptionName: filter_9hkcdqhiqor_84pk01t2k9 - isExtra: false - isNew: false - operator: == - sqlExpression: null - subject: is_software_dev - - clause: WHERE - comparator: 'NULL' - expressionType: SIMPLE - filterOptionName: filter_d5l1qwsthl_okyuouvmors - isExtra: false - isNew: false - operator: '!=' - sqlExpression: null - subject: first_time_developer - - clause: WHERE - comparator: 'NULL' - expressionType: SIMPLE - filterOptionName: filter_95548uvadi_f990s8nzl4 - isExtra: false - isNew: false - operator: '!=' - sqlExpression: null - subject: communite_time + - clause: WHERE + comparator: "1" + expressionType: SIMPLE + filterOptionName: filter_9hkcdqhiqor_84pk01t2k9 + isExtra: false + isNew: false + operator: == + sqlExpression: null + subject: is_software_dev + - clause: WHERE + comparator: "NULL" + expressionType: SIMPLE + filterOptionName: filter_d5l1qwsthl_okyuouvmors + isExtra: false + isNew: false + operator: "!=" + sqlExpression: null + subject: first_time_developer + - clause: WHERE + comparator: "NULL" + expressionType: SIMPLE + filterOptionName: filter_95548uvadi_f990s8nzl4 + isExtra: false + isNew: false + operator: "!=" + sqlExpression: null + subject: communite_time color_scheme: supersetColors datasource: 42__table granularity_sqla: time_start - groupby: - - first_time_developer - - communite_time + source: first_time_developer + target: communite_time label_colors: {} metric: count queryFields: @@ -59,7 +58,7 @@ params: row_limit: 10000 time_range: No filter url_params: {} - viz_type: sankey + viz_type: sankey_v2 cache_timeout: null uuid: 067c4a1e-ae03-4c0c-8e2a-d2c0f4bf43c3 version: 1.0.0 diff --git a/superset/examples/configs/charts/FCC New Coder Survey/How_do_you_prefer_to_work.yaml b/superset/examples/configs/charts/FCC New Coder Survey/How_do_you_prefer_to_work.yaml index c37c4f0bda1cf..55594c52d86fc 100644 --- a/superset/examples/configs/charts/FCC New Coder Survey/How_do_you_prefer_to_work.yaml +++ b/superset/examples/configs/charts/FCC New Coder Survey/How_do_you_prefer_to_work.yaml @@ -15,61 +15,60 @@ # specific language governing permissions and limitations # under the License. slice_name: How do you prefer to work? -viz_type: heatmap +viz_type: heatmap_v2 params: adhoc_filters: - - clause: WHERE - comparator: '0' - expressionType: SIMPLE - filterOptionName: filter_v65f0j14bk_35oi0g94srk - isExtra: false - isNew: false - operator: == - sqlExpression: null - subject: is_software_dev - - clause: WHERE - comparator: 'NULL' - expressionType: SIMPLE - filterOptionName: filter_qb5ionb8wcq_ki4aimey4do - isExtra: false - isNew: false - operator: '!=' - sqlExpression: null - subject: school_degree - - clause: WHERE - comparator: 'NULL' - expressionType: SIMPLE - filterOptionName: filter_3n0z71frg5c_xqnl179to7 - isExtra: false - isNew: false - operator: '!=' - sqlExpression: null - subject: job_pref - all_columns_x: job_pref - all_columns_y: school_degree + - clause: WHERE + comparator: "0" + expressionType: SIMPLE + filterOptionName: filter_v65f0j14bk_35oi0g94srk + isExtra: false + isNew: false + operator: == + sqlExpression: null + subject: is_software_dev + - clause: WHERE + comparator: "NULL" + expressionType: SIMPLE + filterOptionName: filter_qb5ionb8wcq_ki4aimey4do + isExtra: false + isNew: false + operator: "!=" + sqlExpression: null + subject: school_degree + - clause: WHERE + comparator: "NULL" + expressionType: SIMPLE + filterOptionName: filter_3n0z71frg5c_xqnl179to7 + isExtra: false + isNew: false + operator: "!=" + sqlExpression: null + subject: job_pref + x_axis: job_pref + groupby: school_degree bottom_margin: auto - canvas_image_rendering: pixelated datasource: 42__table granularity_sqla: time_start left_margin: auto linear_color_scheme: blue_white_yellow metric: count - normalize_across: heatmap + normalize_across: heatmap_v2 queryFields: metric: metrics row_limit: null show_legend: true - show_perc: true + show_percentage: true slice_id: 1367 sort_x_axis: alpha_asc sort_y_axis: alpha_asc time_range: No filter url_params: {} - viz_type: heatmap + viz_type: heatmap_v2 xscale_interval: null - y_axis_bounds: - - null - - null + value_bounds: + - null + - null y_axis_format: SMART_NUMBER yscale_interval: null cache_timeout: null diff --git a/superset/examples/configs/charts/FCC New Coder Survey/How_much_do_you_expect_to_earn_0_-_100k.yaml b/superset/examples/configs/charts/FCC New Coder Survey/How_much_do_you_expect_to_earn_0_-_100k.yaml index 60fe3a03e5110..4211ca5b57a63 100644 --- a/superset/examples/configs/charts/FCC New Coder Survey/How_much_do_you_expect_to_earn_0_-_100k.yaml +++ b/superset/examples/configs/charts/FCC New Coder Survey/How_much_do_you_expect_to_earn_0_-_100k.yaml @@ -15,80 +15,80 @@ # specific language governing permissions and limitations # under the License. slice_name: How much do you expect to earn? ($0 - 100k) -viz_type: histogram +viz_type: histogram_v2 params: adhoc_filters: - - clause: WHERE - comparator: Aspiring Developer - expressionType: SIMPLE - filterOptionName: filter_dfz5l631lx_lb7f2rlmjdl - isExtra: false - isNew: false - operator: == - sqlExpression: null - subject: developer_type - - clause: WHERE - comparator: '200000' - expressionType: SIMPLE - filterOptionName: filter_6nmi4fk837u_6lvcpn3zzvf - isExtra: false - isNew: false - operator: <= - sqlExpression: null - subject: expected_earn + - clause: WHERE + comparator: Aspiring Developer + expressionType: SIMPLE + filterOptionName: filter_dfz5l631lx_lb7f2rlmjdl + isExtra: false + isNew: false + operator: == + sqlExpression: null + subject: developer_type + - clause: WHERE + comparator: "200000" + expressionType: SIMPLE + filterOptionName: filter_6nmi4fk837u_6lvcpn3zzvf + isExtra: false + isNew: false + operator: <= + sqlExpression: null + subject: expected_earn all_columns_x: - - expected_earn + - expected_earn color_scheme: supersetColors datasource: 42__table granularity_sqla: time_start groupby: null label_colors: - '0': '#FCC700' - '1': '#A868B7' - '15': '#3CCCCB' - '30': '#A38F79' - '45': '#8FD3E4' - : '#5AC189' - Female: '#454E7C' - From Home: '#1FA8C9' - I: '#FEC0A1' - In an Office (with Other Developers): '#9EE5E5' - Less: '#ACE1C4' - Male: '#666666' - More: '#A1A6BD' - 'No': '#666666' - No Answer: '#D3B3DA' - No Preference: '#D1C6BC' - No,: '#FF7F44' - No, not an ethnic minority: '#1FA8C9' - 'No: Not Willing to': '#FDE380' - Ph.D.: '#FCC700' - Prefer: '#5AC189' - Prefer not to say: '#E04355' - 'Yes': '#FF7F44' - Yes,: '#1FA8C9' - Yes, an ethnic minority: '#454E7C' - 'Yes: Willing To': '#EFA1AA' - age: '#1FA8C9' - associate's degree: '#A868B7' - bachelor's degree: '#3CCCCB' - expected_earn: '#B2B2B2' - high school diploma or equivalent (GED): '#A38F79' - last_yr_income: '#E04355' - master's degree (non-professional): '#8FD3E4' - no high school (secondary school): '#A1A6BD' - professional degree (MBA, MD, JD, etc.): '#ACE1C4' - some college credit, no degree: '#FEC0A1' - some high school: '#B2B2B2' - trade, technical, or vocational training: '#EFA1AA' - link_length: '10' + "0": "#FCC700" + "1": "#A868B7" + "15": "#3CCCCB" + "30": "#A38F79" + "45": "#8FD3E4" + : "#5AC189" + Female: "#454E7C" + From Home: "#1FA8C9" + I: "#FEC0A1" + In an Office (with Other Developers): "#9EE5E5" + Less: "#ACE1C4" + Male: "#666666" + More: "#A1A6BD" + "No": "#666666" + No Answer: "#D3B3DA" + No Preference: "#D1C6BC" + No,: "#FF7F44" + No, not an ethnic minority: "#1FA8C9" + "No: Not Willing to": "#FDE380" + Ph.D.: "#FCC700" + Prefer: "#5AC189" + Prefer not to say: "#E04355" + "Yes": "#FF7F44" + Yes,: "#1FA8C9" + Yes, an ethnic minority: "#454E7C" + "Yes: Willing To": "#EFA1AA" + age: "#1FA8C9" + associate's degree: "#A868B7" + bachelor's degree: "#3CCCCB" + expected_earn: "#B2B2B2" + high school diploma or equivalent (GED): "#A38F79" + last_yr_income: "#E04355" + master's degree (non-professional): "#8FD3E4" + no high school (secondary school): "#A1A6BD" + professional degree (MBA, MD, JD, etc.): "#ACE1C4" + some college credit, no degree: "#FEC0A1" + some high school: "#B2B2B2" + trade, technical, or vocational training: "#EFA1AA" + link_length: "10" queryFields: groupby: groupby row_limit: null slice_id: 1366 time_range: No filter url_params: {} - viz_type: histogram + viz_type: histogram_v2 cache_timeout: null uuid: 6d0ceb30-2008-d19c-d285-cf77dc764433 version: 1.0.0 diff --git a/superset/examples/configs/charts/FCC New Coder Survey/Last_Year_Income_Distribution.yaml b/superset/examples/configs/charts/FCC New Coder Survey/Last_Year_Income_Distribution.yaml index 9d17de60ef4db..413448f0a8a38 100644 --- a/superset/examples/configs/charts/FCC New Coder Survey/Last_Year_Income_Distribution.yaml +++ b/superset/examples/configs/charts/FCC New Coder Survey/Last_Year_Income_Distribution.yaml @@ -15,41 +15,41 @@ # specific language governing permissions and limitations # under the License. slice_name: Last Year Income Distribution -viz_type: histogram +viz_type: histogram_v2 params: adhoc_filters: - - clause: WHERE - comparator: Currently A Developer - expressionType: SIMPLE - filterOptionName: filter_fvi0jg9aii_2lekqrhy7qk - isExtra: false - isNew: false - operator: == - sqlExpression: null - subject: developer_type - - clause: WHERE - comparator: '100000' - expressionType: SIMPLE - filterOptionName: filter_khdc3iypzjg_3g6h02b4f2p - isExtra: false - isNew: false - operator: <= - sqlExpression: null - subject: last_yr_income + - clause: WHERE + comparator: Currently A Developer + expressionType: SIMPLE + filterOptionName: filter_fvi0jg9aii_2lekqrhy7qk + isExtra: false + isNew: false + operator: == + sqlExpression: null + subject: developer_type + - clause: WHERE + comparator: "100000" + expressionType: SIMPLE + filterOptionName: filter_khdc3iypzjg_3g6h02b4f2p + isExtra: false + isNew: false + operator: <= + sqlExpression: null + subject: last_yr_income all_columns_x: - - last_yr_income + - last_yr_income color_scheme: supersetColors datasource: 42__table granularity_sqla: time_start groupby: [] label_colors: {} - link_length: '10' + link_length: "10" queryFields: groupby: groupby row_limit: null time_range: No filter url_params: {} - viz_type: histogram + viz_type: histogram_v2 cache_timeout: null uuid: a2ec5256-94b4-43c4-b8c7-b83f70c5d4df version: 1.0.0 diff --git a/superset/examples/configs/charts/Featured Charts/Area.yaml b/superset/examples/configs/charts/Featured Charts/Area.yaml index de053bfa69a2e..157aaff70a72b 100644 --- a/superset/examples/configs/charts/Featured Charts/Area.yaml +++ b/superset/examples/configs/charts/Featured Charts/Area.yaml @@ -73,7 +73,7 @@ params: y_axis_title_position: Left sort_series_type: sum color_scheme: supersetColors - seriesType: line + seriesType: echarts_timeseries_line opacity: 0.2 only_total: true markerSize: 6 diff --git a/superset/examples/configs/charts/Featured Charts/Line.yaml b/superset/examples/configs/charts/Featured Charts/Line.yaml index 285121f741d3a..4f241cf81fa58 100644 --- a/superset/examples/configs/charts/Featured Charts/Line.yaml +++ b/superset/examples/configs/charts/Featured Charts/Line.yaml @@ -50,7 +50,7 @@ params: y_axis_title_position: Left sort_series_type: sum color_scheme: supersetColors - seriesType: line + seriesType: echarts_timeseries_line only_total: true opacity: 0.2 markerSize: 6 diff --git a/superset/examples/configs/charts/Featured Charts/Mixed.yaml b/superset/examples/configs/charts/Featured Charts/Mixed.yaml index 86cd8df7b9040..fc63e11a93a8b 100644 --- a/superset/examples/configs/charts/Featured Charts/Mixed.yaml +++ b/superset/examples/configs/charts/Featured Charts/Mixed.yaml @@ -78,11 +78,11 @@ params: y_axis_title_margin: 15 y_axis_title_position: Left color_scheme: supersetColors - seriesType: line + seriesType: echarts_timeseries_line opacity: 0.2 markerSize: 6 yAxisIndex: 1 - seriesTypeB: bar + seriesTypeB: echarts_timeseries_bar opacityB: 0.2 markerSizeB: 6 yAxisIndexB: 0 diff --git a/superset/examples/configs/charts/Slack Dashboard/Cross_Channel_Relationship_heatmap_2786.yaml b/superset/examples/configs/charts/Slack Dashboard/Cross_Channel_Relationship_heatmap_2786.yaml index e08a915f80835..f06924f37db95 100644 --- a/superset/examples/configs/charts/Slack Dashboard/Cross_Channel_Relationship_heatmap_2786.yaml +++ b/superset/examples/configs/charts/Slack Dashboard/Cross_Channel_Relationship_heatmap_2786.yaml @@ -14,14 +14,13 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -slice_name: Cross Channel Relationship heatmap -viz_type: heatmap +slice_name: Cross Channel Relationship heatmap_v2 +viz_type: heatmap_v2 params: adhoc_filters: [] - all_columns_x: channel_1 - all_columns_y: channel_2 + x_axis: channel_1 + groupby: channel_2 bottom_margin: auto - canvas_image_rendering: pixelated datasource: 35__table left_margin: auto linear_color_scheme: schemeBlues @@ -45,20 +44,20 @@ params: label: SUM(cnt) optionName: metric_i1djbl8i2y_2vdl690dkyu sqlExpression: null - normalize_across: heatmap + normalize_across: heatmap_v2 row_limit: 1000 show_legend: true - show_perc: false + show_percentage: false show_values: true sort_x_axis: alpha_asc sort_y_axis: alpha_asc time_range: No filter url_params: {} - viz_type: heatmap + viz_type: heatmap_v2 xscale_interval: null - y_axis_bounds: - - null - - null + value_bounds: + - null + - null y_axis_format: SMART_NUMBER yscale_interval: null cache_timeout: null diff --git a/superset/examples/configs/charts/Slack Dashboard/Messages_per_Channel.yaml b/superset/examples/configs/charts/Slack Dashboard/Messages_per_Channel.yaml index c24918af73872..a1c5e33c021ce 100644 --- a/superset/examples/configs/charts/Slack Dashboard/Messages_per_Channel.yaml +++ b/superset/examples/configs/charts/Slack Dashboard/Messages_per_Channel.yaml @@ -15,18 +15,18 @@ # specific language governing permissions and limitations # under the License. slice_name: Messages per Channel -viz_type: area +viz_type: echarts_area params: adhoc_filters: - - clause: WHERE - comparator: github-notifications - expressionType: SIMPLE - filterOptionName: filter_7ud3u2eujnw_1pmeehxvw0b - isExtra: false - isNew: false - operator: '!=' - sqlExpression: null - subject: name + - clause: WHERE + comparator: github-notifications + expressionType: SIMPLE + filterOptionName: filter_7ud3u2eujnw_1pmeehxvw0b + isExtra: false + isNew: false + operator: "!=" + sqlExpression: null + subject: name annotation_layers: [] bottom_margin: auto color_scheme: supersetColors @@ -34,44 +34,44 @@ params: datasource: 56__table granularity_sqla: ts groupby: - - name + - name label_colors: - '0': '#1FA8C9' - '1': '#454E7C' - announcements: '#A868B7' - apache-releases: '#666666' - beginners: '#666666' - commits: '#E04355' - community-feedback: '#EFA1AA' - contributing: '#8FD3E4' - cypress-tests: '#FDE380' - dashboard-filters: '#FCC700' - dashboard-level-access: '#D1C6BC' - dashboards: '#3CCCCB' - design: '#1FA8C9' - developers: '#9EE5E5' - embedded-dashboards: '#ACE1C4' - feature-requests: '#454E7C' - general: '#3CCCCB' - github-notifications: '#E04355' - globalnav_search: '#A1A6BD' - graduation: '#A1A6BD' - helm-k8-deployment: '#FEC0A1' - introductions: '#5AC189' - jobs: '#FF7F44' - localization: '#5AC189' - newsletter: '#FF7F44' - product_feedback: '#D3B3DA' - pull-requests: '#A38F79' - superset-champions: '#FCC700' - superset_prod_reports: '#A868B7' - superset_stage_alerts: '#A38F79' - support: '#8FD3E4' - visualization_plugins: '#B2B2B2' + "0": "#1FA8C9" + "1": "#454E7C" + announcements: "#A868B7" + apache-releases: "#666666" + beginners: "#666666" + commits: "#E04355" + community-feedback: "#EFA1AA" + contributing: "#8FD3E4" + cypress-tests: "#FDE380" + dashboard-filters: "#FCC700" + dashboard-level-access: "#D1C6BC" + dashboards: "#3CCCCB" + design: "#1FA8C9" + developers: "#9EE5E5" + embedded-dashboards: "#ACE1C4" + feature-requests: "#454E7C" + general: "#3CCCCB" + github-notifications: "#E04355" + globalnav_search: "#A1A6BD" + graduation: "#A1A6BD" + helm-k8-deployment: "#FEC0A1" + introductions: "#5AC189" + jobs: "#FF7F44" + localization: "#5AC189" + newsletter: "#FF7F44" + product_feedback: "#D3B3DA" + pull-requests: "#A38F79" + superset-champions: "#FCC700" + superset_prod_reports: "#A868B7" + superset_stage_alerts: "#A38F79" + support: "#8FD3E4" + visualization_plugins: "#B2B2B2" limit: 10 line_interpolation: linear metrics: - - count + - count min_periods: 0 order_desc: true queryFields: @@ -89,13 +89,13 @@ params: time_grain_sqla: P1D time_range: No filter url_params: {} - viz_type: area + viz_type: echarts_area x_axis_format: smart_date x_axis_showminmax: true x_ticks_layout: auto y_axis_bounds: - - 0 - - null + - 0 + - null y_axis_format: SMART_NUMBER y_log_scale: false cache_timeout: null diff --git a/superset/examples/configs/charts/Vehicle Sales/Number_of_Deals_for_each_Combination.yaml b/superset/examples/configs/charts/Vehicle Sales/Number_of_Deals_for_each_Combination.yaml index 2d47ee5b69e1e..414b0f0f8aa89 100644 --- a/superset/examples/configs/charts/Vehicle Sales/Number_of_Deals_for_each_Combination.yaml +++ b/superset/examples/configs/charts/Vehicle Sales/Number_of_Deals_for_each_Combination.yaml @@ -15,36 +15,35 @@ # specific language governing permissions and limitations # under the License. slice_name: Number of Deals (for each Combination) -viz_type: heatmap +viz_type: heatmap_v2 params: adhoc_filters: [] - all_columns_x: deal_size - all_columns_y: product_line + x_axis: deal_size + groupby: product_line bottom_margin: 100 - canvas_image_rendering: pixelated datasource: 23__table granularity_sqla: order_date left_margin: 75 linear_color_scheme: schemePuBuGn metric: count - normalize_across: heatmap + normalize_across: heatmap_v2 normalized: true queryFields: metric: metrics row_limit: null show_legend: true - show_perc: true + show_percentage: true show_values: true slice_id: 2810 sort_x_axis: alpha_asc sort_y_axis: alpha_asc time_range: No filter url_params: {} - viz_type: heatmap + viz_type: heatmap_v2 xscale_interval: null - y_axis_bounds: - - null - - null + value_bounds: + - null + - null y_axis_format: SMART_NUMBER yscale_interval: null cache_timeout: null diff --git a/superset/examples/configs/charts/Vehicle Sales/Proportion_of_Revenue_by_Product_Line.yaml b/superset/examples/configs/charts/Vehicle Sales/Proportion_of_Revenue_by_Product_Line.yaml index b24afc025d444..5ccb305b66cea 100644 --- a/superset/examples/configs/charts/Vehicle Sales/Proportion_of_Revenue_by_Product_Line.yaml +++ b/superset/examples/configs/charts/Vehicle Sales/Proportion_of_Revenue_by_Product_Line.yaml @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. slice_name: Proportion of Revenue by Product Line -viz_type: area +viz_type: echarts_area params: adhoc_filters: [] annotation_layers: [] @@ -26,29 +26,29 @@ params: datasource: 23__table granularity_sqla: order_date groupby: - - product_line + - product_line label_colors: {} line_interpolation: linear metrics: - - aggregate: SUM - column: - column_name: sales - description: null - expression: null - filterable: true - groupby: true - id: 917 - is_dttm: false - optionName: _col_Sales - python_date_format: null - type: DOUBLE PRECISION - verbose_name: null - expressionType: SIMPLE - hasCustomLabel: false - isNew: false - label: (Sales) - optionName: metric_3is69ofceho_6d0ezok7ry6 - sqlExpression: null + - aggregate: SUM + column: + column_name: sales + description: null + expression: null + filterable: true + groupby: true + id: 917 + is_dttm: false + optionName: _col_Sales + python_date_format: null + type: DOUBLE PRECISION + verbose_name: null + expressionType: SIMPLE + hasCustomLabel: false + isNew: false + label: (Sales) + optionName: metric_3is69ofceho_6d0ezok7ry6 + sqlExpression: null order_desc: true queryFields: groupby: groupby @@ -60,14 +60,14 @@ params: show_legend: true stacked_style: stack time_grain_sqla: P1M - time_range: '2003-01-01T00:00:00 : 2005-06-01T00:00:00' + time_range: "2003-01-01T00:00:00 : 2005-06-01T00:00:00" url_params: {} - viz_type: area + viz_type: echarts_area x_axis_format: smart_date x_ticks_layout: auto y_axis_bounds: - - null - - null + - null + - null y_axis_format: SMART_NUMBER cache_timeout: null uuid: 08aff161-f60c-4cb3-a225-dc9b1140d2e3 diff --git a/superset/examples/configs/charts/Vehicle Sales/Quarterly_Sales.yaml b/superset/examples/configs/charts/Vehicle Sales/Quarterly_Sales.yaml index 9479fd9d3f96e..a575c47bed1dc 100644 --- a/superset/examples/configs/charts/Vehicle Sales/Quarterly_Sales.yaml +++ b/superset/examples/configs/charts/Vehicle Sales/Quarterly_Sales.yaml @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. slice_name: Quarterly Sales -viz_type: bar +viz_type: echarts_timeseries_bar params: adhoc_filters: [] annotation_layers: [] @@ -26,37 +26,37 @@ params: granularity_sqla: order_date groupby: [] label_colors: - Classic Cars: '#5AC189' - Motorcycles: '#666666' - Planes: '#FCC700' - QuantityOrdered: '#454E7C' - SUM(Sales): '#1FA8C9' - Ships: '#A868B7' - Trains: '#3CCCCB' - Trucks and Buses: '#E04355' - Vintage Cars: '#FF7F44' + Classic Cars: "#5AC189" + Motorcycles: "#666666" + Planes: "#FCC700" + QuantityOrdered: "#454E7C" + SUM(Sales): "#1FA8C9" + Ships: "#A868B7" + Trains: "#3CCCCB" + Trucks and Buses: "#E04355" + Vintage Cars: "#FF7F44" left_margin: auto line_interpolation: linear metrics: - - aggregate: SUM - column: - column_name: sales - description: null - expression: null - filterable: true - groupby: true - id: 917 - is_dttm: false - optionName: _col_Sales - python_date_format: null - type: DOUBLE PRECISION - verbose_name: null - expressionType: SIMPLE - hasCustomLabel: false - isNew: false - label: SUM(Sales) - optionName: metric_tjn8bh6y44_7o4etwsqhal - sqlExpression: null + - aggregate: SUM + column: + column_name: sales + description: null + expression: null + filterable: true + groupby: true + id: 917 + is_dttm: false + optionName: _col_Sales + python_date_format: null + type: DOUBLE PRECISION + verbose_name: null + expressionType: SIMPLE + hasCustomLabel: false + isNew: false + label: SUM(Sales) + optionName: metric_tjn8bh6y44_7o4etwsqhal + sqlExpression: null order_desc: true queryFields: groupby: groupby @@ -71,13 +71,13 @@ params: time_grain_sqla: P3M time_range: No filter url_params: {} - viz_type: bar - x_axis_format: '%m/%d/%Y' + viz_type: echarts_timeseries_bar + x_axis_format: "%m/%d/%Y" x_axis_label: Quarter starting x_ticks_layout: auto y_axis_bounds: - - null - - null + - null + - null y_axis_format: null y_axis_label: Total Sales cache_timeout: null diff --git a/superset/examples/configs/charts/Vehicle Sales/Quarterly_Sales_By_Product_Line.yaml b/superset/examples/configs/charts/Vehicle Sales/Quarterly_Sales_By_Product_Line.yaml index 35a135e81abcc..7f5e039c8e5ba 100644 --- a/superset/examples/configs/charts/Vehicle Sales/Quarterly_Sales_By_Product_Line.yaml +++ b/superset/examples/configs/charts/Vehicle Sales/Quarterly_Sales_By_Product_Line.yaml @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. slice_name: Quarterly Sales (By Product Line) -viz_type: bar +viz_type: echarts_timeseries_bar params: adhoc_filters: [] annotation_layers: [] @@ -26,39 +26,39 @@ params: datasource: 23__table granularity_sqla: order_date groupby: - - product_line + - product_line label_colors: - Classic Cars: '#5AC189' - Motorcycles: '#666666' - Planes: '#FCC700' - QuantityOrdered: '#454E7C' - SUM(Sales): '#1FA8C9' - Ships: '#A868B7' - Trains: '#3CCCCB' - Trucks and Buses: '#E04355' - Vintage Cars: '#FF7F44' + Classic Cars: "#5AC189" + Motorcycles: "#666666" + Planes: "#FCC700" + QuantityOrdered: "#454E7C" + SUM(Sales): "#1FA8C9" + Ships: "#A868B7" + Trains: "#3CCCCB" + Trucks and Buses: "#E04355" + Vintage Cars: "#FF7F44" left_margin: auto line_interpolation: linear metrics: - - aggregate: SUM - column: - column_name: sales - description: null - expression: null - filterable: true - groupby: true - id: 917 - is_dttm: false - optionName: _col_Sales - python_date_format: null - type: DOUBLE PRECISION - verbose_name: null - expressionType: SIMPLE - hasCustomLabel: false - isNew: false - label: SUM(Sales) - optionName: metric_tjn8bh6y44_7o4etwsqhal - sqlExpression: null + - aggregate: SUM + column: + column_name: sales + description: null + expression: null + filterable: true + groupby: true + id: 917 + is_dttm: false + optionName: _col_Sales + python_date_format: null + type: DOUBLE PRECISION + verbose_name: null + expressionType: SIMPLE + hasCustomLabel: false + isNew: false + label: SUM(Sales) + optionName: metric_tjn8bh6y44_7o4etwsqhal + sqlExpression: null order_desc: true queryFields: groupby: groupby @@ -74,13 +74,13 @@ params: time_grain_sqla: P3M time_range: No filter url_params: {} - viz_type: bar - x_axis_format: '%m/%d/%Y' + viz_type: echarts_timeseries_bar + x_axis_format: "%m/%d/%Y" x_axis_label: Quarter starting x_ticks_layout: "45\xB0" y_axis_bounds: - - null - - null + - null + - null y_axis_format: null y_axis_label: Revenue ($) cache_timeout: null diff --git a/superset/examples/configs/charts/Vehicle Sales/Revenue_by_Deal_Size.yaml b/superset/examples/configs/charts/Vehicle Sales/Revenue_by_Deal_Size.yaml index 547d1804beb3c..7ab51d2efffab 100644 --- a/superset/examples/configs/charts/Vehicle Sales/Revenue_by_Deal_Size.yaml +++ b/superset/examples/configs/charts/Vehicle Sales/Revenue_by_Deal_Size.yaml @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. slice_name: Revenue by Deal Size -viz_type: bar +viz_type: echarts_timeseries_bar params: adhoc_filters: [] annotation_layers: [] @@ -27,30 +27,30 @@ params: datasource: 23__table granularity_sqla: order_date groupby: - - deal_size + - deal_size label_colors: {} left_margin: auto line_interpolation: linear metrics: - - aggregate: SUM - column: - column_name: sales - description: null - expression: null - filterable: true - groupby: true - id: 917 - is_dttm: false - optionName: _col_Sales - python_date_format: null - type: DOUBLE PRECISION - verbose_name: null - expressionType: SIMPLE - hasCustomLabel: false - isNew: false - label: (Sales) - optionName: metric_3is69ofceho_6d0ezok7ry6 - sqlExpression: null + - aggregate: SUM + column: + column_name: sales + description: null + expression: null + filterable: true + groupby: true + id: 917 + is_dttm: false + optionName: _col_Sales + python_date_format: null + type: DOUBLE PRECISION + verbose_name: null + expressionType: SIMPLE + hasCustomLabel: false + isNew: false + label: (Sales) + optionName: metric_3is69ofceho_6d0ezok7ry6 + sqlExpression: null order_desc: true queryFields: groupby: groupby @@ -61,14 +61,14 @@ params: show_brush: auto show_legend: true time_grain_sqla: P1M - time_range: '2003-01-01T00:00:00 : 2005-06-01T00:00:00' + time_range: "2003-01-01T00:00:00 : 2005-06-01T00:00:00" url_params: {} - viz_type: bar + viz_type: echarts_timeseries_bar x_axis_format: smart_date x_ticks_layout: auto y_axis_bounds: - - null - - null + - null + - null y_axis_format: SMART_NUMBER cache_timeout: null uuid: f065a533-2e13-42b9-bd19-801a21700dff diff --git a/superset/examples/configs/charts/Video Game Sales/Games_per_Genre_over_time.yaml b/superset/examples/configs/charts/Video Game Sales/Games_per_Genre_over_time.yaml index f3e61d694940a..d57f8f64c9e54 100644 --- a/superset/examples/configs/charts/Video Game Sales/Games_per_Genre_over_time.yaml +++ b/superset/examples/configs/charts/Video Game Sales/Games_per_Genre_over_time.yaml @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. slice_name: Games per Genre over time -viz_type: line +viz_type: echarts_timeseries_line params: adhoc_filters: [] annotation_layers: [] @@ -26,64 +26,64 @@ params: datasource: 21__table granularity_sqla: year groupby: - - genre + - genre label_colors: - '0': '#1FA8C9' - '1': '#454E7C' - '2600': '#666666' - 3DO: '#B2B2B2' - 3DS: '#D1C6BC' - Action: '#1FA8C9' - Adventure: '#454E7C' - DC: '#A38F79' - DS: '#8FD3E4' - Europe: '#5AC189' - Fighting: '#5AC189' - GB: '#FDE380' - GBA: '#ACE1C4' - GC: '#5AC189' - GEN: '#3CCCCB' - GG: '#EFA1AA' - Japan: '#FF7F44' - Microsoft Game Studios: '#D1C6BC' - Misc: '#FF7F44' - N64: '#1FA8C9' - NES: '#9EE5E5' - NG: '#A1A6BD' - Nintendo: '#D3B3DA' - North America: '#666666' - Other: '#E04355' - PC: '#EFA1AA' - PCFX: '#FDE380' - PS: '#A1A6BD' - PS2: '#FCC700' - PS3: '#3CCCCB' - PS4: '#B2B2B2' - PSP: '#FEC0A1' - PSV: '#FCC700' - Platform: '#666666' - Puzzle: '#E04355' - Racing: '#FCC700' - Role-Playing: '#A868B7' - SAT: '#A868B7' - SCD: '#8FD3E4' - SNES: '#454E7C' - Shooter: '#3CCCCB' - Simulation: '#A38F79' - Sports: '#8FD3E4' - Strategy: '#A1A6BD' - TG16: '#FEC0A1' - Take-Two Interactive: '#9EE5E5' - WS: '#ACE1C4' - Wii: '#A38F79' - WiiU: '#E04355' - X360: '#A868B7' - XB: '#D3B3DA' - XOne: '#FF7F44' + "0": "#1FA8C9" + "1": "#454E7C" + "2600": "#666666" + 3DO: "#B2B2B2" + 3DS: "#D1C6BC" + Action: "#1FA8C9" + Adventure: "#454E7C" + DC: "#A38F79" + DS: "#8FD3E4" + Europe: "#5AC189" + Fighting: "#5AC189" + GB: "#FDE380" + GBA: "#ACE1C4" + GC: "#5AC189" + GEN: "#3CCCCB" + GG: "#EFA1AA" + Japan: "#FF7F44" + Microsoft Game Studios: "#D1C6BC" + Misc: "#FF7F44" + N64: "#1FA8C9" + NES: "#9EE5E5" + NG: "#A1A6BD" + Nintendo: "#D3B3DA" + North America: "#666666" + Other: "#E04355" + PC: "#EFA1AA" + PCFX: "#FDE380" + PS: "#A1A6BD" + PS2: "#FCC700" + PS3: "#3CCCCB" + PS4: "#B2B2B2" + PSP: "#FEC0A1" + PSV: "#FCC700" + Platform: "#666666" + Puzzle: "#E04355" + Racing: "#FCC700" + Role-Playing: "#A868B7" + SAT: "#A868B7" + SCD: "#8FD3E4" + SNES: "#454E7C" + Shooter: "#3CCCCB" + Simulation: "#A38F79" + Sports: "#8FD3E4" + Strategy: "#A1A6BD" + TG16: "#FEC0A1" + Take-Two Interactive: "#9EE5E5" + WS: "#ACE1C4" + Wii: "#A38F79" + WiiU: "#E04355" + X360: "#A868B7" + XB: "#D3B3DA" + XOne: "#FF7F44" left_margin: auto line_interpolation: linear metrics: - - count + - count order_desc: true queryFields: groupby: groupby @@ -98,18 +98,19 @@ params: time_grain_sqla: null time_range: No filter url_params: - preselect_filters: '{"1389": {"platform": ["PS", "PS2", "PS3", "PS4"], "genre": + preselect_filters: + '{"1389": {"platform": ["PS", "PS2", "PS3", "PS4"], "genre": null, "__time_range": "No filter"}}' - viz_type: line + viz_type: echarts_timeseries_line x_axis_format: smart_date x_axis_label: Year Published x_axis_showminmax: true x_ticks_layout: auto y_axis_bounds: - - null - - null + - null + - null y_axis_format: SMART_NUMBER - y_axis_label: '# of Games Published' + y_axis_label: "# of Games Published" y_axis_showminmax: true cache_timeout: null uuid: 0f8976aa-7bb4-40c7-860b-64445a51aaaf diff --git a/superset/examples/configs/charts/Video Game Sales/Popular_Genres_Across_Platforms.yaml b/superset/examples/configs/charts/Video Game Sales/Popular_Genres_Across_Platforms.yaml index a0a111d8dbc71..f73b29c835008 100644 --- a/superset/examples/configs/charts/Video Game Sales/Popular_Genres_Across_Platforms.yaml +++ b/superset/examples/configs/charts/Video Game Sales/Popular_Genres_Across_Platforms.yaml @@ -15,34 +15,33 @@ # specific language governing permissions and limitations # under the License. slice_name: Popular Genres Across Platforms -viz_type: heatmap +viz_type: heatmap_v2 params: adhoc_filters: [] - all_columns_x: platform - all_columns_y: genre + x_axis: platform + groupby: genre bottom_margin: auto - canvas_image_rendering: pixelated datasource: 64__table granularity_sqla: year left_margin: auto linear_color_scheme: blue_white_yellow metric: count - normalize_across: heatmap + normalize_across: heatmap_v2 queryFields: metric: metrics row_limit: 10000 show_legend: true - show_perc: true + show_percentage: true show_values: true sort_x_axis: alpha_asc sort_y_axis: alpha_asc time_range: No filter url_params: {} - viz_type: heatmap + viz_type: heatmap_v2 xscale_interval: null - y_axis_bounds: - - null - - null + value_bounds: + - null + - null y_axis_format: SMART_NUMBER yscale_interval: null cache_timeout: null diff --git a/superset/examples/configs/charts/Video Game Sales/Rise__Fall_of_Video_Game_Consoles.yaml b/superset/examples/configs/charts/Video Game Sales/Rise__Fall_of_Video_Game_Consoles.yaml index 1e73b329c91ea..b370ba6db5698 100644 --- a/superset/examples/configs/charts/Video Game Sales/Rise__Fall_of_Video_Game_Consoles.yaml +++ b/superset/examples/configs/charts/Video Game Sales/Rise__Fall_of_Video_Game_Consoles.yaml @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. slice_name: Rise & Fall of Video Game Consoles -viz_type: area +viz_type: echarts_area params: adhoc_filters: [] annotation_layers: [] @@ -26,81 +26,81 @@ params: datasource: 21__table granularity_sqla: year groupby: - - platform + - platform label_colors: - '0': '#1FA8C9' - '1': '#454E7C' - '2600': '#666666' - 3DO: '#B2B2B2' - 3DS: '#D1C6BC' - Action: '#1FA8C9' - Adventure: '#454E7C' - DC: '#A38F79' - DS: '#8FD3E4' - Europe: '#5AC189' - Fighting: '#5AC189' - GB: '#FDE380' - GBA: '#ACE1C4' - GC: '#5AC189' - GEN: '#3CCCCB' - GG: '#EFA1AA' - Japan: '#FF7F44' - Microsoft Game Studios: '#D1C6BC' - Misc: '#FF7F44' - N64: '#1FA8C9' - NES: '#9EE5E5' - NG: '#A1A6BD' - Nintendo: '#D3B3DA' - North America: '#666666' - Other: '#E04355' - PC: '#EFA1AA' - PCFX: '#FDE380' - PS: '#A1A6BD' - PS2: '#FCC700' - PS3: '#3CCCCB' - PS4: '#B2B2B2' - PSP: '#FEC0A1' - PSV: '#FCC700' - Platform: '#666666' - Puzzle: '#E04355' - Racing: '#FCC700' - Role-Playing: '#A868B7' - SAT: '#A868B7' - SCD: '#8FD3E4' - SNES: '#454E7C' - Shooter: '#3CCCCB' - Simulation: '#A38F79' - Sports: '#8FD3E4' - Strategy: '#A1A6BD' - TG16: '#FEC0A1' - Take-Two Interactive: '#9EE5E5' - WS: '#ACE1C4' - Wii: '#A38F79' - WiiU: '#E04355' - X360: '#A868B7' - XB: '#D3B3DA' - XOne: '#FF7F44' + "0": "#1FA8C9" + "1": "#454E7C" + "2600": "#666666" + 3DO: "#B2B2B2" + 3DS: "#D1C6BC" + Action: "#1FA8C9" + Adventure: "#454E7C" + DC: "#A38F79" + DS: "#8FD3E4" + Europe: "#5AC189" + Fighting: "#5AC189" + GB: "#FDE380" + GBA: "#ACE1C4" + GC: "#5AC189" + GEN: "#3CCCCB" + GG: "#EFA1AA" + Japan: "#FF7F44" + Microsoft Game Studios: "#D1C6BC" + Misc: "#FF7F44" + N64: "#1FA8C9" + NES: "#9EE5E5" + NG: "#A1A6BD" + Nintendo: "#D3B3DA" + North America: "#666666" + Other: "#E04355" + PC: "#EFA1AA" + PCFX: "#FDE380" + PS: "#A1A6BD" + PS2: "#FCC700" + PS3: "#3CCCCB" + PS4: "#B2B2B2" + PSP: "#FEC0A1" + PSV: "#FCC700" + Platform: "#666666" + Puzzle: "#E04355" + Racing: "#FCC700" + Role-Playing: "#A868B7" + SAT: "#A868B7" + SCD: "#8FD3E4" + SNES: "#454E7C" + Shooter: "#3CCCCB" + Simulation: "#A38F79" + Sports: "#8FD3E4" + Strategy: "#A1A6BD" + TG16: "#FEC0A1" + Take-Two Interactive: "#9EE5E5" + WS: "#ACE1C4" + Wii: "#A38F79" + WiiU: "#E04355" + X360: "#A868B7" + XB: "#D3B3DA" + XOne: "#FF7F44" line_interpolation: linear metrics: - - aggregate: SUM - column: - column_name: global_sales - description: null - expression: null - filterable: true - groupby: true - id: 887 - is_dttm: false - optionName: _col_Global_Sales - python_date_format: null - type: DOUBLE PRECISION - verbose_name: null - expressionType: SIMPLE - hasCustomLabel: false - isNew: false - label: SUM(Global_Sales) - optionName: metric_ufl75addr8c_oqqhdumirpn - sqlExpression: null + - aggregate: SUM + column: + column_name: global_sales + description: null + expression: null + filterable: true + groupby: true + id: 887 + is_dttm: false + optionName: _col_Global_Sales + python_date_format: null + type: DOUBLE PRECISION + verbose_name: null + expressionType: SIMPLE + hasCustomLabel: false + isNew: false + label: SUM(Global_Sales) + optionName: metric_ufl75addr8c_oqqhdumirpn + sqlExpression: null order_desc: true queryFields: groupby: groupby @@ -115,16 +115,17 @@ params: time_grain_sqla: null time_range: No filter url_params: - preselect_filters: '{"1389": {"platform": ["PS", "PS2", "PS3", "PS4"], "genre": + preselect_filters: + '{"1389": {"platform": ["PS", "PS2", "PS3", "PS4"], "genre": null, "__time_range": "No filter"}}' - viz_type: area + viz_type: echarts_area x_axis_format: smart_date x_axis_label: Year Published x_axis_showminmax: true x_ticks_layout: auto y_axis_bounds: - - null - - null + - null + - null y_axis_format: SMART_NUMBER cache_timeout: null uuid: 83b0e2d0-d38b-d980-ed8e-e1c9846361b6 diff --git a/superset/examples/configs/charts/Video Game Sales/Top_10_Games_Proportion_of_Sales_in_Markets.yaml b/superset/examples/configs/charts/Video Game Sales/Top_10_Games_Proportion_of_Sales_in_Markets.yaml index d8a80afaedd75..344122d25d3a5 100644 --- a/superset/examples/configs/charts/Video Game Sales/Top_10_Games_Proportion_of_Sales_in_Markets.yaml +++ b/superset/examples/configs/charts/Video Game Sales/Top_10_Games_Proportion_of_Sales_in_Markets.yaml @@ -14,19 +14,19 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -slice_name: 'Top 10 Games: Proportion of Sales in Markets' -viz_type: dist_bar +slice_name: "Top 10 Games: Proportion of Sales in Markets" +viz_type: echarts_timeseries_bar params: adhoc_filters: - - clause: WHERE - comparator: '10' - expressionType: SIMPLE - filterOptionName: filter_juemdnqji5_d6fm8tuf4rc - isExtra: false - isNew: false - operator: <= - sqlExpression: null - subject: rank + - clause: WHERE + comparator: "10" + expressionType: SIMPLE + filterOptionName: filter_juemdnqji5_d6fm8tuf4rc + isExtra: false + isNew: false + operator: <= + sqlExpression: null + subject: rank bar_stacked: true bottom_margin: auto color_scheme: supersetColors @@ -35,85 +35,85 @@ params: datasource: 21__table granularity_sqla: year groupby: - - name + - name label_colors: {} metrics: - - aggregate: SUM - column: - column_name: na_sales - description: null - expression: null - filterable: true - groupby: true - id: 883 - is_dttm: false - optionName: _col_NA_Sales - python_date_format: null - type: DOUBLE PRECISION - verbose_name: null - expressionType: SIMPLE - hasCustomLabel: true - isNew: false - label: North America - optionName: metric_a943v7wg5g_0mm03hrsmpf - sqlExpression: null - - aggregate: SUM - column: - column_name: eu_sales - description: null - expression: null - filterable: true - groupby: true - id: 884 - is_dttm: false - optionName: _col_EU_Sales - python_date_format: null - type: DOUBLE PRECISION - verbose_name: null - expressionType: SIMPLE - hasCustomLabel: true - isNew: false - label: Europe - optionName: metric_bibau54x0rb_dwrjtqkbyso - sqlExpression: null - - aggregate: SUM - column: - column_name: jp_sales - description: null - expression: null - filterable: true - groupby: true - id: 885 - is_dttm: false - optionName: _col_JP_Sales - python_date_format: null - type: DOUBLE PRECISION - verbose_name: null - expressionType: SIMPLE - hasCustomLabel: true - isNew: false - label: Japan - optionName: metric_06whpr2oyhw_4l88xxu6zvd - sqlExpression: null - - aggregate: SUM - column: - column_name: other_sales - description: null - expression: null - filterable: true - groupby: true - id: 886 - is_dttm: false - optionName: _col_Other_Sales - python_date_format: null - type: DOUBLE PRECISION - verbose_name: null - expressionType: SIMPLE - hasCustomLabel: true - isNew: false - label: Other - optionName: metric_pcx05ioxums_ibr16zvi74 - sqlExpression: null + - aggregate: SUM + column: + column_name: na_sales + description: null + expression: null + filterable: true + groupby: true + id: 883 + is_dttm: false + optionName: _col_NA_Sales + python_date_format: null + type: DOUBLE PRECISION + verbose_name: null + expressionType: SIMPLE + hasCustomLabel: true + isNew: false + label: North America + optionName: metric_a943v7wg5g_0mm03hrsmpf + sqlExpression: null + - aggregate: SUM + column: + column_name: eu_sales + description: null + expression: null + filterable: true + groupby: true + id: 884 + is_dttm: false + optionName: _col_EU_Sales + python_date_format: null + type: DOUBLE PRECISION + verbose_name: null + expressionType: SIMPLE + hasCustomLabel: true + isNew: false + label: Europe + optionName: metric_bibau54x0rb_dwrjtqkbyso + sqlExpression: null + - aggregate: SUM + column: + column_name: jp_sales + description: null + expression: null + filterable: true + groupby: true + id: 885 + is_dttm: false + optionName: _col_JP_Sales + python_date_format: null + type: DOUBLE PRECISION + verbose_name: null + expressionType: SIMPLE + hasCustomLabel: true + isNew: false + label: Japan + optionName: metric_06whpr2oyhw_4l88xxu6zvd + sqlExpression: null + - aggregate: SUM + column: + column_name: other_sales + description: null + expression: null + filterable: true + groupby: true + id: 886 + is_dttm: false + optionName: _col_Other_Sales + python_date_format: null + type: DOUBLE PRECISION + verbose_name: null + expressionType: SIMPLE + hasCustomLabel: true + isNew: false + label: Other + optionName: metric_pcx05ioxums_ibr16zvi74 + sqlExpression: null queryFields: columns: groupby groupby: groupby @@ -123,7 +123,7 @@ params: slice_id: 3546 time_range: No filter url_params: {} - viz_type: dist_bar + viz_type: echarts_timeseries_bar x_ticks_layout: staggered y_axis_format: SMART_NUMBER cache_timeout: null diff --git a/superset/examples/configs/charts/Video Game Sales/Total_Sales_per_Market_Grouped_by_Genre.yaml b/superset/examples/configs/charts/Video Game Sales/Total_Sales_per_Market_Grouped_by_Genre.yaml index 196631386a10d..78ddf4bbeca49 100644 --- a/superset/examples/configs/charts/Video Game Sales/Total_Sales_per_Market_Grouped_by_Genre.yaml +++ b/superset/examples/configs/charts/Video Game Sales/Total_Sales_per_Market_Grouped_by_Genre.yaml @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. slice_name: Total Sales per Market (Grouped by Genre) -viz_type: dist_bar +viz_type: echarts_timeseries_bar params: adhoc_filters: [] bar_stacked: true @@ -26,137 +26,137 @@ params: datasource: 21__table granularity_sqla: year groupby: - - genre + - genre label_colors: - '0': '#1FA8C9' - '1': '#454E7C' - '2600': '#666666' - 3DO: '#B2B2B2' - 3DS: '#D1C6BC' - Action: '#1FA8C9' - Adventure: '#454E7C' - DC: '#A38F79' - DS: '#8FD3E4' - Europe: '#5AC189' - Fighting: '#5AC189' - GB: '#FDE380' - GBA: '#ACE1C4' - GC: '#5AC189' - GEN: '#3CCCCB' - GG: '#EFA1AA' - Japan: '#FF7F44' - Microsoft Game Studios: '#D1C6BC' - Misc: '#FF7F44' - N64: '#1FA8C9' - NES: '#9EE5E5' - NG: '#A1A6BD' - Nintendo: '#D3B3DA' - North America: '#666666' - Other: '#E04355' - PC: '#EFA1AA' - PCFX: '#FDE380' - PS: '#A1A6BD' - PS2: '#FCC700' - PS3: '#3CCCCB' - PS4: '#B2B2B2' - PSP: '#FEC0A1' - PSV: '#FCC700' - Platform: '#666666' - Puzzle: '#E04355' - Racing: '#FCC700' - Role-Playing: '#A868B7' - SAT: '#A868B7' - SCD: '#8FD3E4' - SNES: '#454E7C' - Shooter: '#3CCCCB' - Simulation: '#A38F79' - Sports: '#8FD3E4' - Strategy: '#A1A6BD' - TG16: '#FEC0A1' - Take-Two Interactive: '#9EE5E5' - WS: '#ACE1C4' - Wii: '#A38F79' - WiiU: '#E04355' - X360: '#A868B7' - XB: '#D3B3DA' - XOne: '#FF7F44' + "0": "#1FA8C9" + "1": "#454E7C" + "2600": "#666666" + 3DO: "#B2B2B2" + 3DS: "#D1C6BC" + Action: "#1FA8C9" + Adventure: "#454E7C" + DC: "#A38F79" + DS: "#8FD3E4" + Europe: "#5AC189" + Fighting: "#5AC189" + GB: "#FDE380" + GBA: "#ACE1C4" + GC: "#5AC189" + GEN: "#3CCCCB" + GG: "#EFA1AA" + Japan: "#FF7F44" + Microsoft Game Studios: "#D1C6BC" + Misc: "#FF7F44" + N64: "#1FA8C9" + NES: "#9EE5E5" + NG: "#A1A6BD" + Nintendo: "#D3B3DA" + North America: "#666666" + Other: "#E04355" + PC: "#EFA1AA" + PCFX: "#FDE380" + PS: "#A1A6BD" + PS2: "#FCC700" + PS3: "#3CCCCB" + PS4: "#B2B2B2" + PSP: "#FEC0A1" + PSV: "#FCC700" + Platform: "#666666" + Puzzle: "#E04355" + Racing: "#FCC700" + Role-Playing: "#A868B7" + SAT: "#A868B7" + SCD: "#8FD3E4" + SNES: "#454E7C" + Shooter: "#3CCCCB" + Simulation: "#A38F79" + Sports: "#8FD3E4" + Strategy: "#A1A6BD" + TG16: "#FEC0A1" + Take-Two Interactive: "#9EE5E5" + WS: "#ACE1C4" + Wii: "#A38F79" + WiiU: "#E04355" + X360: "#A868B7" + XB: "#D3B3DA" + XOne: "#FF7F44" metrics: - - aggregate: SUM - column: - column_name: na_sales - description: null - expression: null - filterable: true - groupby: true - id: 883 - is_dttm: false - optionName: _col_NA_Sales - python_date_format: null - type: DOUBLE PRECISION - verbose_name: null - expressionType: SIMPLE - hasCustomLabel: true - isNew: false - label: North America - optionName: metric_3pl6jwmyd72_p9o4j2xxgyp - sqlExpression: null - - aggregate: SUM - column: - column_name: eu_sales - description: null - expression: null - filterable: true - groupby: true - id: 884 - is_dttm: false - optionName: _col_EU_Sales - python_date_format: null - type: DOUBLE PRECISION - verbose_name: null - expressionType: SIMPLE - hasCustomLabel: true - isNew: false - label: Europe - optionName: metric_e8rdyfxxjdu_6dgyhf7xcne - sqlExpression: null - - aggregate: SUM - column: - column_name: jp_sales - description: null - expression: null - filterable: true - groupby: true - id: 885 - is_dttm: false - optionName: _col_JP_Sales - python_date_format: null - type: DOUBLE PRECISION - verbose_name: null - expressionType: SIMPLE - hasCustomLabel: true - isNew: false - label: Japan - optionName: metric_6gesefugzy6_517l3wowdwu - sqlExpression: null - - aggregate: SUM - column: - column_name: other_sales - description: null - expression: null - filterable: true - groupby: true - id: 886 - is_dttm: false - optionName: _col_Other_Sales - python_date_format: null - type: DOUBLE PRECISION - verbose_name: null - expressionType: SIMPLE - hasCustomLabel: true - isNew: false - label: Other - optionName: metric_cf6kbre28f_2sg5b5pfq5a - sqlExpression: null + - aggregate: SUM + column: + column_name: na_sales + description: null + expression: null + filterable: true + groupby: true + id: 883 + is_dttm: false + optionName: _col_NA_Sales + python_date_format: null + type: DOUBLE PRECISION + verbose_name: null + expressionType: SIMPLE + hasCustomLabel: true + isNew: false + label: North America + optionName: metric_3pl6jwmyd72_p9o4j2xxgyp + sqlExpression: null + - aggregate: SUM + column: + column_name: eu_sales + description: null + expression: null + filterable: true + groupby: true + id: 884 + is_dttm: false + optionName: _col_EU_Sales + python_date_format: null + type: DOUBLE PRECISION + verbose_name: null + expressionType: SIMPLE + hasCustomLabel: true + isNew: false + label: Europe + optionName: metric_e8rdyfxxjdu_6dgyhf7xcne + sqlExpression: null + - aggregate: SUM + column: + column_name: jp_sales + description: null + expression: null + filterable: true + groupby: true + id: 885 + is_dttm: false + optionName: _col_JP_Sales + python_date_format: null + type: DOUBLE PRECISION + verbose_name: null + expressionType: SIMPLE + hasCustomLabel: true + isNew: false + label: Japan + optionName: metric_6gesefugzy6_517l3wowdwu + sqlExpression: null + - aggregate: SUM + column: + column_name: other_sales + description: null + expression: null + filterable: true + groupby: true + id: 886 + is_dttm: false + optionName: _col_Other_Sales + python_date_format: null + type: DOUBLE PRECISION + verbose_name: null + expressionType: SIMPLE + hasCustomLabel: true + isNew: false + label: Other + optionName: metric_cf6kbre28f_2sg5b5pfq5a + sqlExpression: null order_bars: false queryFields: columns: groupby @@ -169,9 +169,10 @@ params: slice_id: 3548 time_range: No filter url_params: - preselect_filters: '{"1389": {"platform": ["PS", "PS2", "PS3", "PS4"], "genre": + preselect_filters: + '{"1389": {"platform": ["PS", "PS2", "PS3", "PS4"], "genre": null, "__time_range": "No filter"}}' - viz_type: dist_bar + viz_type: echarts_timeseries_bar x_axis_label: Genre x_ticks_layout: flat y_axis_format: SMART_NUMBER diff --git a/superset/examples/country_map.py b/superset/examples/country_map.py index 53f4a0b874ff3..a093044b70915 100644 --- a/superset/examples/country_map.py +++ b/superset/examples/country_map.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. import datetime +import logging import pandas as pd from sqlalchemy import BigInteger, Date, inspect, String @@ -35,6 +36,8 @@ misc_dash_slices, ) +logger = logging.getLogger(__name__) + def load_country_map_data(only_metadata: bool = False, force: bool = False) -> None: """Loading data for map with country map""" @@ -73,10 +76,10 @@ def load_country_map_data(only_metadata: bool = False, force: bool = False) -> N }, index=False, ) - print("Done loading table!") - print("-" * 80) + logger.debug("Done loading table!") + logger.debug("-" * 80) - print("Creating table reference") + logger.debug("Creating table reference") table = get_table_connector_registry() obj = db.session.query(table).filter_by(table_name=tbl_name).first() if not obj: @@ -108,7 +111,7 @@ def load_country_map_data(only_metadata: bool = False, force: bool = False) -> N "select_country": "france", } - print("Creating a slice") + logger.debug("Creating a slice") slc = Slice( slice_name="Birth in France by department in 2016", viz_type="country_map", diff --git a/superset/examples/css_templates.py b/superset/examples/css_templates.py index 91bb54c157752..d534c78185e81 100644 --- a/superset/examples/css_templates.py +++ b/superset/examples/css_templates.py @@ -14,15 +14,18 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import logging import textwrap from superset import db from superset.models.core import CssTemplate +logger = logging.getLogger(__name__) + def load_css_templates() -> None: """Loads 2 css templates to demonstrate the feature""" - print("Creating default CSS templates") + logger.debug("Creating default CSS templates") obj = db.session.query(CssTemplate).filter_by(template_name="Flat").first() if not obj: diff --git a/superset/examples/deck.py b/superset/examples/deck.py index 931924dd0879b..137d14434f031 100644 --- a/superset/examples/deck.py +++ b/superset/examples/deck.py @@ -15,6 +15,8 @@ # specific language governing permissions and limitations # under the License. +import logging + from superset import db from superset.models.dashboard import Dashboard from superset.models.slice import Slice @@ -28,6 +30,8 @@ update_slice_ids, ) +logger = logging.getLogger(__name__) + COLOR_RED = {"r": 205, "g": 0, "b": 3, "a": 0.82} POSITION_JSON = """\ { @@ -180,7 +184,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements - print("Loading deck.gl dashboard") + logger.debug("Loading deck.gl dashboard") slices = [] table = get_table_connector_registry() tbl = db.session.query(table).filter_by(table_name="long_lat").first() @@ -210,7 +214,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements "viz_type": "deck_scatter", } - print("Creating Scatterplot slice") + logger.debug("Creating Scatterplot slice") slc = Slice( slice_name="Deck.gl Scatterplot", viz_type="deck_scatter", @@ -245,7 +249,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements "time_grain_sqla": None, "groupby": [], } - print("Creating Screen Grid slice") + logger.debug("Creating Screen Grid slice") slc = Slice( slice_name="Deck.gl Screen grid", viz_type="deck_screengrid", @@ -281,7 +285,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements "time_grain_sqla": None, "groupby": [], } - print("Creating Hex slice") + logger.debug("Creating Hex slice") slc = Slice( slice_name="Deck.gl Hexagons", viz_type="deck_hex", @@ -318,7 +322,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements "time_grain_sqla": None, "groupby": [], } - print("Creating Grid slice") + logger.debug("Creating Grid slice") slc = Slice( slice_name="Deck.gl Grid", viz_type="deck_grid", @@ -409,7 +413,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements "legend_position": "tr", } - print("Creating Polygon slice") + logger.debug("Creating Polygon slice") slc = Slice( slice_name="Deck.gl Polygons", viz_type="deck_polygon", @@ -459,7 +463,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements "stroke_width": 1, } - print("Creating Arc slice") + logger.debug("Creating Arc slice") slc = Slice( slice_name="Deck.gl Arcs", viz_type="deck_arc", @@ -511,7 +515,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements "js_onclick_href": "", } - print("Creating Path slice") + logger.debug("Creating Path slice") slc = Slice( slice_name="Deck.gl Path", viz_type="deck_path", @@ -526,7 +530,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements slices.append(slc) slug = "deck" - print("Creating a dashboard") + logger.debug("Creating a dashboard") title = "deck.gl Demo" dash = db.session.query(Dashboard).filter_by(slug=slug).first() diff --git a/superset/examples/energy.py b/superset/examples/energy.py index d7e46ec5d8c31..1b2bfa72a204c 100644 --- a/superset/examples/energy.py +++ b/superset/examples/energy.py @@ -14,6 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import logging import textwrap import pandas as pd @@ -29,11 +30,14 @@ from .helpers import ( get_example_url, + get_slice_json, get_table_connector_registry, merge_slice, misc_dash_slices, ) +logger = logging.getLogger(__name__) + def load_energy( only_metadata: bool = False, force: bool = False, sample: bool = False @@ -61,7 +65,7 @@ def load_energy( method="multi", ) - print("Creating table [wb_health_population] reference") + logger.debug("Creating table [wb_health_population] reference") table = get_table_connector_registry() tbl = db.session.query(table).filter_by(table_name=tbl_name).first() if not tbl: @@ -81,21 +85,19 @@ def load_energy( slc = Slice( slice_name="Energy Sankey", - viz_type="sankey", + viz_type="sankey_v2", datasource_type=DatasourceType.TABLE, datasource_id=tbl.id, params=textwrap.dedent( """\ { "collapsed_fieldsets": "", - "groupby": [ - "source", - "target" - ], + "source": "source", + "target": "target", "metric": "sum__value", "row_limit": "5000", "slice_name": "Energy Sankey", - "viz_type": "sankey" + "viz_type": "sankey_v2" } """ ), @@ -129,25 +131,18 @@ def load_energy( slc = Slice( slice_name="Heatmap", - viz_type="heatmap", + viz_type="heatmap_v2", datasource_type=DatasourceType.TABLE, datasource_id=tbl.id, - params=textwrap.dedent( - """\ - { - "all_columns_x": "source", - "all_columns_y": "target", - "canvas_image_rendering": "pixelated", - "collapsed_fieldsets": "", - "linear_color_scheme": "blue_white_yellow", - "metric": "sum__value", - "normalize_across": "heatmap", - "slice_name": "Heatmap", - "viz_type": "heatmap", - "xscale_interval": "1", - "yscale_interval": "1" - } - """ + params=get_slice_json( + defaults={}, + viz_type="heatmap_v2", + x_axis="source", + groupby="target", + legend_type="continuous", + metric="sum__value", + sort_x_axis="value_asc", + sort_y_axis="value_asc", ), ) misc_dash_slices.add(slc.slice_name) diff --git a/superset/examples/flights.py b/superset/examples/flights.py index f8659c24d07fc..81a31bb970aec 100644 --- a/superset/examples/flights.py +++ b/superset/examples/flights.py @@ -14,6 +14,8 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import logging + import pandas as pd from sqlalchemy import DateTime, inspect @@ -23,6 +25,8 @@ from .helpers import get_example_url, get_table_connector_registry +logger = logging.getLogger(__name__) + def load_flights(only_metadata: bool = False, force: bool = False) -> None: """Loading random time series data from a zip file in the repo""" @@ -67,4 +71,4 @@ def load_flights(only_metadata: bool = False, force: bool = False) -> None: tbl.database = database tbl.filter_select_enabled = True tbl.fetch_metadata() - print("Done loading table!") + logger.debug("Done loading table!") diff --git a/superset/examples/long_lat.py b/superset/examples/long_lat.py index c4d48ce25bf31..87bcbd9fdc512 100644 --- a/superset/examples/long_lat.py +++ b/superset/examples/long_lat.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. import datetime +import logging import random import geohash @@ -35,6 +36,8 @@ misc_dash_slices, ) +logger = logging.getLogger(__name__) + def load_long_lat_data(only_metadata: bool = False, force: bool = False) -> None: """Loading lat/long data from a csv file in the repo""" @@ -85,10 +88,10 @@ def load_long_lat_data(only_metadata: bool = False, force: bool = False) -> None }, index=False, ) - print("Done loading table!") - print("-" * 80) + logger.debug("Done loading table!") + logger.debug("-" * 80) - print("Creating table reference") + logger.debug("Creating table reference") table = get_table_connector_registry() obj = db.session.query(table).filter_by(table_name=tbl_name).first() if not obj: @@ -112,7 +115,7 @@ def load_long_lat_data(only_metadata: bool = False, force: bool = False) -> None "row_limit": 500000, } - print("Creating a slice") + logger.debug("Creating a slice") slc = Slice( slice_name="Mapbox Long/Lat", viz_type="mapbox", diff --git a/superset/examples/misc_dashboard.py b/superset/examples/misc_dashboard.py index 4a7079e2cddc3..1e896f8c7ff8c 100644 --- a/superset/examples/misc_dashboard.py +++ b/superset/examples/misc_dashboard.py @@ -14,6 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import logging import textwrap from superset import db @@ -22,13 +23,15 @@ from .helpers import update_slice_ids +logger = logging.getLogger(__name__) + DASH_SLUG = "misc_charts" def load_misc_dashboard() -> None: """Loading a dashboard featuring misc charts""" - print("Creating the dashboard") + logger.debug("Creating the dashboard") db.session.expunge_all() dash = db.session.query(Dashboard).filter_by(slug=DASH_SLUG).first() diff --git a/superset/examples/multiformat_time_series.py b/superset/examples/multiformat_time_series.py index 9cfe44c1994c1..af357385f97f5 100644 --- a/superset/examples/multiformat_time_series.py +++ b/superset/examples/multiformat_time_series.py @@ -14,6 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import logging from typing import Optional import pandas as pd @@ -33,6 +34,8 @@ misc_dash_slices, ) +logger = logging.getLogger(__name__) + def load_multiformat_time_series( # pylint: disable=too-many-locals only_metadata: bool = False, force: bool = False @@ -75,10 +78,10 @@ def load_multiformat_time_series( # pylint: disable=too-many-locals }, index=False, ) - print("Done loading table!") - print("-" * 80) + logger.debug("Done loading table!") + logger.debug("-" * 80) - print(f"Creating table [{tbl_name}] reference") + logger.debug(f"Creating table [{tbl_name}] reference") table = get_table_connector_registry() obj = db.session.query(table).filter_by(table_name=tbl_name).first() if not obj: @@ -105,7 +108,7 @@ def load_multiformat_time_series( # pylint: disable=too-many-locals obj.fetch_metadata() tbl = obj - print("Creating Heatmap charts") + logger.debug("Creating Heatmap charts") for i, col in enumerate(tbl.columns): slice_data = { "metrics": ["count"], diff --git a/superset/examples/paris.py b/superset/examples/paris.py index 928e2294072a4..56a6affe432b7 100644 --- a/superset/examples/paris.py +++ b/superset/examples/paris.py @@ -15,6 +15,8 @@ # specific language governing permissions and limitations # under the License. +import logging + import pandas as pd from sqlalchemy import inspect, String, Text @@ -25,6 +27,8 @@ from .helpers import get_example_url, get_table_connector_registry +logger = logging.getLogger(__name__) + def load_paris_iris_geojson(only_metadata: bool = False, force: bool = False) -> None: tbl_name = "paris_iris_mapping" @@ -53,7 +57,7 @@ def load_paris_iris_geojson(only_metadata: bool = False, force: bool = False) -> index=False, ) - print(f"Creating table {tbl_name} reference") + logger.debug(f"Creating table {tbl_name} reference") table = get_table_connector_registry() tbl = db.session.query(table).filter_by(table_name=tbl_name).first() if not tbl: diff --git a/superset/examples/random_time_series.py b/superset/examples/random_time_series.py index 10ece826b6a1d..f473565235ae9 100644 --- a/superset/examples/random_time_series.py +++ b/superset/examples/random_time_series.py @@ -14,6 +14,8 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import logging + import pandas as pd from sqlalchemy import DateTime, inspect, String @@ -30,6 +32,8 @@ merge_slice, ) +logger = logging.getLogger(__name__) + def load_random_time_series_data( only_metadata: bool = False, force: bool = False @@ -59,10 +63,10 @@ def load_random_time_series_data( dtype={"ds": DateTime if database.backend != "presto" else String(255)}, index=False, ) - print("Done loading table!") - print("-" * 80) + logger.debug("Done loading table!") + logger.debug("-" * 80) - print(f"Creating table [{tbl_name}] reference") + logger.debug(f"Creating table [{tbl_name}] reference") table = get_table_connector_registry() obj = db.session.query(table).filter_by(table_name=tbl_name).first() if not obj: @@ -85,7 +89,7 @@ def load_random_time_series_data( "subdomain_granularity": "day", } - print("Creating a slice") + logger.debug("Creating a slice") slc = Slice( slice_name="Calendar Heatmap", viz_type="cal_heatmap", diff --git a/superset/examples/sf_population_polygons.py b/superset/examples/sf_population_polygons.py index b8d5527ed247b..239ab04a17a6d 100644 --- a/superset/examples/sf_population_polygons.py +++ b/superset/examples/sf_population_polygons.py @@ -15,6 +15,8 @@ # specific language governing permissions and limitations # under the License. +import logging + import pandas as pd from sqlalchemy import BigInteger, Float, inspect, Text @@ -25,6 +27,8 @@ from .helpers import get_example_url, get_table_connector_registry +logger = logging.getLogger(__name__) + def load_sf_population_polygons( only_metadata: bool = False, force: bool = False @@ -55,7 +59,7 @@ def load_sf_population_polygons( index=False, ) - print(f"Creating table {tbl_name} reference") + logger.debug(f"Creating table {tbl_name} reference") table = get_table_connector_registry() tbl = db.session.query(table).filter_by(table_name=tbl_name).first() if not tbl: diff --git a/superset/examples/supported_charts_dashboard.py b/superset/examples/supported_charts_dashboard.py index c605bf88cc571..2c4d190ec2810 100644 --- a/superset/examples/supported_charts_dashboard.py +++ b/superset/examples/supported_charts_dashboard.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. # pylint: disable=too-many-lines +import logging import textwrap from sqlalchemy import inspect @@ -36,6 +37,7 @@ ) DASH_SLUG = "supported_charts_dash" +logger = logging.getLogger(__name__) def create_slices(tbl: SqlaTable) -> list[Slice]: @@ -124,7 +126,7 @@ def create_slices(tbl: SqlaTable) -> list[Slice]: ), Slice( **slice_kwargs, - slice_name="Bar Chart V2", + slice_name="Bar Chart", viz_type="echarts_timeseries_bar", params=get_slice_json( defaults, @@ -156,17 +158,6 @@ def create_slices(tbl: SqlaTable) -> list[Slice]: adhoc_filters=[], ), ), - Slice( - **slice_kwargs, - slice_name="Bar Chart", - viz_type="dist_bar", - params=get_slice_json( - defaults, - viz_type="dist_bar", - metrics=["sum__num"], - groupby=["gender"], - ), - ), # --------------------- # TIER 2 # --------------------- @@ -304,13 +295,15 @@ def create_slices(tbl: SqlaTable) -> list[Slice]: Slice( **slice_kwargs, slice_name="Heatmap Chart", - viz_type="heatmap", + viz_type="heatmap_v2", params=get_slice_json( defaults, - viz_type="funnel", + viz_type="heatmap_v2", metric="sum__num", - all_columns_x="gender", - all_columns_y="state", + x_axis="gender", + groupby="state", + sort_x_axis="value_asc", + sort_y_axis="value_asc", ), ), Slice( @@ -385,12 +378,13 @@ def create_slices(tbl: SqlaTable) -> list[Slice]: Slice( **slice_kwargs, slice_name="Sankey Chart", - viz_type="sankey", + viz_type="sankey_v2", params=get_slice_json( defaults, - viz_type="sankey", + viz_type="sankey_v2", metric="sum__num", - groupby=["gender", "state"], + source="gender", + target="state", ), ), Slice( @@ -453,7 +447,7 @@ def load_supported_charts_dashboard() -> None: ) create_slices(obj) - print("Creating the dashboard") + logger.debug("Creating the dashboard") db.session.expunge_all() dash = db.session.query(Dashboard).filter_by(slug=DASH_SLUG).first() @@ -561,7 +555,7 @@ def load_supported_charts_dashboard() -> None: "meta": { "chartId": 6, "height": 50, - "sliceName": "Bar Chart V2", + "sliceName": "Bar Chart", "width": 4 }, "type": "CHART" @@ -617,23 +611,6 @@ def load_supported_charts_dashboard() -> None: }, "type": "CHART" }, - "CHART-10": { - "children": [], - "parents": [ - "ROOT_ID", - "TABS-TOP", - "TAB-TOP-1", - "ROW-4" - ], - "id": "CHART-10", - "meta": { - "chartId": 10, - "height": 50, - "sliceName": "Bar Chart", - "width": 4 - }, - "type": "CHART" - }, "CHART-11": { "children": [], "parents": [ diff --git a/superset/examples/tabbed_dashboard.py b/superset/examples/tabbed_dashboard.py index b44c2a6d2be91..ef0272cd608c0 100644 --- a/superset/examples/tabbed_dashboard.py +++ b/superset/examples/tabbed_dashboard.py @@ -14,6 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import logging import textwrap from superset import db @@ -22,11 +23,13 @@ from .helpers import update_slice_ids +logger = logging.getLogger(__name__) + def load_tabbed_dashboard(_: bool = False) -> None: """Creating a tabbed dashboard""" - print("Creating a dashboard with nested tabs") + logger.debug("Creating a dashboard with nested tabs") slug = "tabbed_dash" dash = db.session.query(Dashboard).filter_by(slug=slug).first() diff --git a/superset/examples/world_bank.py b/superset/examples/world_bank.py index a9c06dfa2942a..b683d8fb911df 100644 --- a/superset/examples/world_bank.py +++ b/superset/examples/world_bank.py @@ -14,6 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import logging import os import pandas as pd @@ -38,6 +39,8 @@ from superset.utils import core as utils, json from superset.utils.core import DatasourceType +logger = logging.getLogger(__name__) + def load_world_bank_health_n_pop( # pylint: disable=too-many-locals only_metadata: bool = False, @@ -79,7 +82,7 @@ def load_world_bank_health_n_pop( # pylint: disable=too-many-locals index=False, ) - print("Creating table [wb_health_population] reference") + logger.debug("Creating table [wb_health_population] reference") table = get_table_connector_registry() tbl = db.session.query(table).filter_by(table_name=tbl_name).first() if not tbl: @@ -115,7 +118,7 @@ def load_world_bank_health_n_pop( # pylint: disable=too-many-locals for slc in slices: merge_slice(slc) - print("Creating a World's Health Bank dashboard") + logger.debug("Creating a World's Health Bank dashboard") dash_name = "World Bank's Data" slug = "world_health" dash = db.session.query(Dashboard).filter_by(slug=slug).first() @@ -192,12 +195,12 @@ def create_slices(tbl: BaseDatasource) -> list[Slice]: ), Slice( slice_name="Growth Rate", - viz_type="line", + viz_type="echarts_timeseries_line", datasource_type=DatasourceType.TABLE, datasource_id=tbl.id, params=get_slice_json( defaults, - viz_type="line", + viz_type="echarts_timeseries_line", since="1960-01-01", metrics=["sum__SP_POP_TOTL"], num_period_compare="10", @@ -278,14 +281,14 @@ def create_slices(tbl: BaseDatasource) -> list[Slice]: ), Slice( slice_name="World's Pop Growth", - viz_type="area", + viz_type="echarts_area", datasource_type=DatasourceType.TABLE, datasource_id=tbl.id, params=get_slice_json( defaults, since="1960-01-01", until="now", - viz_type="area", + viz_type="echarts_area", groupby=["region"], metrics=metrics, ), diff --git a/superset/extensions/metadb.py b/superset/extensions/metadb.py index 3a95ab5d7b75d..8409aed241e2e 100644 --- a/superset/extensions/metadb.py +++ b/superset/extensions/metadb.py @@ -412,7 +412,7 @@ def get_data( connection = engine.connect() rows = connection.execute(query) for i, row in enumerate(rows): - data = dict(zip(self.columns, row)) + data = dict(zip(self.columns, row, strict=False)) data["rowid"] = data[self._rowid] if self._rowid else i yield data diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py index ee7fcf9ef15d0..a57325c359076 100644 --- a/superset/initialization/__init__.py +++ b/superset/initialization/__init__.py @@ -157,6 +157,7 @@ def init_views(self) -> None: from superset.row_level_security.api import RLSRestApi from superset.security.api import SecurityRestApi from superset.sqllab.api import SqlLabRestApi + from superset.sqllab.permalink.api import SqlLabPermalinkRestApi from superset.tags.api import TagRestApi from superset.views.alerts import AlertView, ReportView from superset.views.all_entities import TaggedObjectsModelView @@ -178,7 +179,6 @@ def init_views(self) -> None: from superset.views.dynamic_plugins import DynamicPluginsView from superset.views.error_handling import set_app_error_handlers from superset.views.explore import ExplorePermalinkView, ExploreView - from superset.views.key_value import KV from superset.views.log.api import LogRestApi from superset.views.log.views import LogModelView from superset.views.sql_lab.views import ( @@ -227,6 +227,7 @@ def init_views(self) -> None: appbuilder.add_api(SavedQueryRestApi) appbuilder.add_api(TagRestApi) appbuilder.add_api(SqlLabRestApi) + appbuilder.add_api(SqlLabPermalinkRestApi) # # Setup regular views # @@ -304,7 +305,6 @@ def init_views(self) -> None: appbuilder.add_view_no_menu(EmbeddedView) appbuilder.add_view_no_menu(ExploreView) appbuilder.add_view_no_menu(ExplorePermalinkView) - appbuilder.add_view_no_menu(KV) appbuilder.add_view_no_menu(SavedQueryView) appbuilder.add_view_no_menu(SavedQueryViewApi) appbuilder.add_view_no_menu(SliceAsync) @@ -547,7 +547,12 @@ def configure_feature_flags(self) -> None: feature_flag_manager.init_app(self.superset_app) def configure_sqlglot_dialects(self) -> None: - SQLGLOT_DIALECTS.update(self.config["SQLGLOT_DIALECTS_EXTENSIONS"]) + extensions = self.config["SQLGLOT_DIALECTS_EXTENSIONS"] + + if callable(extensions): + extensions = extensions() + + SQLGLOT_DIALECTS.update(extensions) @transaction() def configure_fab(self) -> None: diff --git a/superset/key_value/shared_entries.py b/superset/key_value/shared_entries.py index c2acafa807520..21a41b560cf40 100644 --- a/superset/key_value/shared_entries.py +++ b/superset/key_value/shared_entries.py @@ -42,6 +42,7 @@ def set_shared_value(key: SharedKey, value: Any) -> None: def get_permalink_salt(key: SharedKey) -> str: salt = get_shared_value(key) if salt is None: - salt = random_key() + # Use a 48 bytes salt + salt = random_key(48) set_shared_value(key, value=salt) return salt diff --git a/superset/key_value/types.py b/superset/key_value/types.py index 348697234f89f..3b2da06493c96 100644 --- a/superset/key_value/types.py +++ b/superset/key_value/types.py @@ -45,11 +45,13 @@ class KeyValueResource(StrEnum): EXPLORE_PERMALINK = "explore_permalink" METASTORE_CACHE = "superset_metastore_cache" LOCK = "lock" + SQLLAB_PERMALINK = "sqllab_permalink" class SharedKey(StrEnum): DASHBOARD_PERMALINK_SALT = "dashboard_permalink_salt" EXPLORE_PERMALINK_SALT = "explore_permalink_salt" + SQLLAB_PERMALINK_SALT = "sqllab_permalink_salt" class KeyValueCodec(ABC): diff --git a/superset/key_value/utils.py b/superset/key_value/utils.py index e0ccbd1f5b289..e7be47311612c 100644 --- a/superset/key_value/utils.py +++ b/superset/key_value/utils.py @@ -31,8 +31,14 @@ HASHIDS_MIN_LENGTH = 11 -def random_key() -> str: - return token_urlsafe(48) +def random_key(nbytes: int = 8) -> str: + """ + Generate a random URL-safe string. + + Args: + nbytes (int): Number of bytes to use for generating the key. Default is 8. + """ + return token_urlsafe(nbytes) def get_filter(resource: KeyValueResource, key: Key) -> KeyValueFilter: diff --git a/superset/migrations/versions/2025-01-08_09-34_d482d51c15ca_remove_legacy_plugins_5_0.py b/superset/migrations/versions/2025-01-08_09-34_d482d51c15ca_remove_legacy_plugins_5_0.py new file mode 100644 index 0000000000000..22b174cf3dc40 --- /dev/null +++ b/superset/migrations/versions/2025-01-08_09-34_d482d51c15ca_remove_legacy_plugins_5_0.py @@ -0,0 +1,78 @@ +# 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. +"""remove_legacy_plugins_5_0 + +Revision ID: d482d51c15ca +Revises: eb1c288c71c4 +Create Date: 2025-01-08 09:34:57.533332 + +""" + +from alembic import op + +from superset import db +from superset.migrations.shared.migrate_viz.processors import ( + MigrateAreaChart, + MigrateBarChart, + MigrateDistBarChart, + MigrateHeatmapChart, + MigrateHistogramChart, + MigrateLineChart, + MigrateSankey, +) + +# revision identifiers, used by Alembic. +revision = "d482d51c15ca" +down_revision = "eb1c288c71c4" + + +def upgrade(): + bind = op.get_bind() + session = db.Session(bind=bind) + try: + MigrateAreaChart.upgrade(session) + MigrateBarChart.upgrade(session) + MigrateDistBarChart.upgrade(session) + MigrateHeatmapChart.upgrade(session) + MigrateHistogramChart.upgrade(session) + MigrateLineChart.upgrade(session) + MigrateSankey.upgrade(session) + session.commit() + except Exception as e: + session.rollback() + raise Exception(f"Error upgrading legacy viz types: {e}") from e + finally: + session.close() + + +def downgrade(): + bind = op.get_bind() + session = db.Session(bind=bind) + try: + MigrateAreaChart.downgrade(session) + MigrateBarChart.downgrade(session) + MigrateDistBarChart.downgrade(session) + MigrateHeatmapChart.downgrade(session) + MigrateHistogramChart.downgrade(session) + MigrateLineChart.downgrade(session) + MigrateSankey.downgrade(session) + session.commit() + except Exception as e: + session.rollback() + raise Exception(f"Error downgrading legacy viz types: {e}") from e + finally: + session.close() diff --git a/superset/models/core.py b/superset/models/core.py index afabea8f9065a..6f32383ab8058 100755 --- a/superset/models/core.py +++ b/superset/models/core.py @@ -74,6 +74,7 @@ ) from superset.models.helpers import AuditMixinNullable, ImportExportMixin, UUIDMixin from superset.result_set import SupersetResultSet +from superset.sql.parse import SQLScript from superset.sql_parse import Table from superset.superset_typing import ( DbapiDescription, @@ -82,7 +83,7 @@ ) from superset.utils import cache as cache_util, core as utils, json from superset.utils.backports import StrEnum -from superset.utils.core import DatasourceName, get_username +from superset.utils.core import get_username from superset.utils.oauth2 import get_oauth2_access_token, OAuth2ClientConfigSchema config = app.config @@ -740,6 +741,7 @@ def compile_sqla_query( qry: Select, catalog: str | None = None, schema: str | None = None, + is_virtual: bool = False, ) -> str: with self.get_sqla_engine(catalog=catalog, schema=schema) as engine: sql = str(qry.compile(engine, compile_kwargs={"literal_binds": True})) @@ -748,6 +750,12 @@ def compile_sqla_query( if engine.dialect.identifier_preparer._double_percents: # noqa sql = sql.replace("%%", "%") + # for nwo we only optimize queries on virtual datasources, since the only + # optimization available is predicate pushdown + if is_feature_enabled("OPTIMIZE_SQL") and is_virtual: + script = SQLScript(sql, self.db_engine_spec.engine).optimize() + sql = script.format() + return sql def select_star( # pylint: disable=too-many-arguments @@ -783,14 +791,14 @@ def safe_sqlalchemy_uri(self) -> str: return self.sqlalchemy_uri @cache_util.memoized_func( - key="db:{self.id}:schema:{schema}:table_list", + key="db:{self.id}:catalog:{catalog}:schema:{schema}:table_list", cache=cache_manager.cache, ) def get_all_table_names_in_schema( self, catalog: str | None, schema: str, - ) -> set[DatasourceName]: + ) -> set[tuple[str, str, str | None]]: """Parameters need to be passed as keyword arguments. For unused parameters, they are referenced in @@ -806,7 +814,7 @@ def get_all_table_names_in_schema( try: with self.get_inspector(catalog=catalog, schema=schema) as inspector: return { - DatasourceName(table, schema, catalog) + (table, schema, catalog) for table in self.db_engine_spec.get_table_names( database=self, inspector=inspector, @@ -817,14 +825,14 @@ def get_all_table_names_in_schema( raise self.db_engine_spec.get_dbapi_mapped_exception(ex) from ex @cache_util.memoized_func( - key="db:{self.id}:schema:{schema}:view_list", + key="db:{self.id}:catalog:{catalog}:schema:{schema}:view_list", cache=cache_manager.cache, ) def get_all_view_names_in_schema( self, catalog: str | None, schema: str, - ) -> set[DatasourceName]: + ) -> set[tuple[str, str, str | None]]: """Parameters need to be passed as keyword arguments. For unused parameters, they are referenced in @@ -840,7 +848,7 @@ def get_all_view_names_in_schema( try: with self.get_inspector(catalog=catalog, schema=schema) as inspector: return { - DatasourceName(view, schema, catalog) + (view, schema, catalog) for view in self.db_engine_spec.get_view_names( database=self, inspector=inspector, @@ -865,7 +873,7 @@ def get_inspector( yield sqla.inspect(engine) @cache_util.memoized_func( - key="db:{self.id}:schema_list", + key="db:{self.id}:catalog:{catalog}:schema_list", cache=cache_manager.cache, ) def get_all_schema_names( diff --git a/superset/models/helpers.py b/superset/models/helpers.py index 9587ec2385efb..1cad46ca62aed 100644 --- a/superset/models/helpers.py +++ b/superset/models/helpers.py @@ -335,13 +335,13 @@ def import_from_dict( # noqa: C901 is_new_obj = True # Create new DB object obj = cls(**dict_rep) - logger.info("Importing new %s %s", obj.__tablename__, str(obj)) + logger.debug("Importing new %s %s", obj.__tablename__, str(obj)) if cls.export_parent and parent: setattr(obj, cls.export_parent, parent) db.session.add(obj) else: is_new_obj = False - logger.info("Updating %s %s", obj.__tablename__, str(obj)) + logger.debug("Updating %s %s", obj.__tablename__, str(obj)) # Update columns for k, v in dict_rep.items(): setattr(obj, k, v) @@ -372,7 +372,7 @@ def import_from_dict( # noqa: C901 db.session.query(child_class).filter(and_(*delete_filters)) ).difference(set(added)) for o in to_delete: - logger.info("Deleting %s %s", child, str(obj)) + logger.debug("Deleting %s %s", child, str(obj)) db.session.delete(o) return obj @@ -883,7 +883,12 @@ def get_query_str_extended( mutate: bool = True, ) -> QueryStringExtended: sqlaq = self.get_sqla_query(**query_obj) - sql = self.database.compile_sqla_query(sqlaq.sqla_query) + sql = self.database.compile_sqla_query( + sqlaq.sqla_query, + catalog=self.catalog, + schema=self.schema, + is_virtual=bool(self.sql), + ) sql = self._apply_cte(sql, sqlaq.cte) if mutate: @@ -1971,7 +1976,7 @@ def get_sqla_query( # pylint: disable=too-many-arguments,too-many-locals,too-ma self.make_orderby_compatible(select_exprs, orderby_exprs) - for col, (orig_col, ascending) in zip(orderby_exprs, orderby): # noqa: B007 + for col, (orig_col, ascending) in zip(orderby_exprs, orderby, strict=False): # noqa: B007 if not db_engine_spec.allows_alias_in_orderby and isinstance(col, Label): # if engine does not allow using SELECT alias in ORDER BY # revert to the underlying column diff --git a/superset/result_set.py b/superset/result_set.py index eca00de4faf5c..f6daa4b99ebb5 100644 --- a/superset/result_set.py +++ b/superset/result_set.py @@ -123,7 +123,9 @@ def __init__( # pylint: disable=too-many-locals # noqa: C901 # fix cursor descriptor with the deduped names deduped_cursor_desc = [ tuple([column_name, *list(description)[1:]]) # noqa: C409 - for column_name, description in zip(column_names, cursor_description) + for column_name, description in zip( + column_names, cursor_description, strict=False + ) ] # generate numpy structured array dtype diff --git a/superset/sql/dialects/__init__.py b/superset/sql/dialects/__init__.py new file mode 100644 index 0000000000000..ab09de3c2a69c --- /dev/null +++ b/superset/sql/dialects/__init__.py @@ -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. + +from .firebolt import Firebolt, FireboltOld + +__all__ = ["Firebolt", "FireboltOld"] diff --git a/superset/sql/dialects/firebolt.py b/superset/sql/dialects/firebolt.py new file mode 100644 index 0000000000000..a4767596f4003 --- /dev/null +++ b/superset/sql/dialects/firebolt.py @@ -0,0 +1,192 @@ +# 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. + +from __future__ import annotations + +from sqlglot import exp, generator, parser +from sqlglot.dialects.dialect import Dialect +from sqlglot.helper import csv +from sqlglot.tokens import TokenType + + +class Firebolt(Dialect): + """ + A sqlglot dialect for Firebolt. + """ + + class Parser(parser.Parser): + """ + Custom parser for Firebolt. + + In Firebolt `NOT` has higher precedence than `IN`, so we need to wrap the + expression in parentheses when we find a negated range. + """ + + UNARY_PARSERS = { + **parser.Parser.UNARY_PARSERS, + TokenType.NOT: lambda self: self.expression( + exp.Not, + this=self._parse_unary(), # pylint: disable=protected-access + ), + } + + def _negate_range( + self, + this: exp.Expression | None = None, + ) -> exp.Expression | None: + if not this: + return this + + return self.expression(exp.Not, this=self.expression(exp.Paren, this=this)) + + class Generator(generator.Generator): + """ + Custom generator for Firebolt. + """ + + TYPE_MAPPING = { + **generator.Generator.TYPE_MAPPING, + exp.DataType.Type.VARBINARY: "BYTEA", + } + + def not_sql(self, expression: exp.Not) -> str: + """ + Parenthesize negated expressions. + + Firebolt requires negated to be wrapped in parentheses, since NOT has higher + precedence than IN. + """ + if isinstance(expression.this, exp.In): + return f"NOT ({self.sql(expression, 'this')})" + + return super().not_sql(expression) + + +class FireboltOld(Firebolt): + """ + Dialect for the old version of Firebolt (https://old.docs.firebolt.io/). + + The main difference is that `UNNEST` is an operator like `JOIN`, instead of a + function. + """ + + class Parser(Firebolt.Parser): + TABLE_ALIAS_TOKENS = Firebolt.Parser.TABLE_ALIAS_TOKENS - {TokenType.UNNEST} + + def _parse_join( + self, + skip_join_token: bool = False, + parse_bracket: bool = False, + ) -> exp.Join | None: + if unnest := self._parse_unnest(): + return self.expression(exp.Join, this=unnest) + + return super()._parse_join(skip_join_token, parse_bracket) + + def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: + if not self._match(TokenType.UNNEST): + return None + + # parse expressions (col1 AS foo), instead of equalities as in the original + # dialect + expressions = self._parse_wrapped_csv(self._parse_expression) + offset = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) + + alias = self._parse_table_alias() if with_alias else None + + if alias: + if self.dialect.UNNEST_COLUMN_ONLY: + if alias.args.get("columns"): + self.raise_error("Unexpected extra column alias in unnest.") + + alias.set("columns", [alias.this]) + alias.set("this", None) + + columns = alias.args.get("columns") or [] + if offset and len(expressions) < len(columns): + offset = columns.pop() + + if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): + self._match(TokenType.ALIAS) + offset = self._parse_id_var( + any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS + ) or exp.to_identifier("offset") + + return self.expression( + exp.Unnest, + expressions=expressions, + alias=alias, + offset=offset, + ) + + class Generator(Firebolt.Generator): + def join_sql(self, expression: exp.Join) -> str: + if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ( + "SEMI", + "ANTI", + ): + side = None + else: + side = expression.side + + op_sql = " ".join( + op + for op in ( + expression.method, + "GLOBAL" if expression.args.get("global") else None, + side, + expression.kind, + expression.hint if self.JOIN_HINTS else None, + ) + if op + ) + match_cond = self.sql(expression, "match_condition") + match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" + on_sql = self.sql(expression, "on") + using = expression.args.get("using") + + if not on_sql and using: + on_sql = csv(*(self.sql(column) for column in using)) + + this = expression.this + this_sql = self.sql(this) + + if exprs := self.expressions(expression): + this_sql = f"{this_sql},{self.seg(exprs)}" + + if on_sql: + on_sql = self.indent(on_sql, skip_first=True) + space = self.seg(" " * self.pad) if self.pretty else " " + if using: + on_sql = f"{space}USING ({on_sql})" + else: + on_sql = f"{space}ON {on_sql}" + elif not op_sql: + # the main difference with the base dialect is the lack of comma before + # an `UNNEST` + if ( + isinstance(this, exp.Lateral) + and this.args.get("cross_apply") is not None + ) or isinstance(this, exp.Unnest): + return f" {this_sql}" + + return f", {this_sql}" + + if op_sql != "STRAIGHT_JOIN": + op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" + + return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}" diff --git a/superset/sql/parse.py b/superset/sql/parse.py index 91d7b51184f21..f5923fecc7c82 100644 --- a/superset/sql/parse.py +++ b/superset/sql/parse.py @@ -17,6 +17,7 @@ from __future__ import annotations +import copy import enum import logging import re @@ -31,9 +32,11 @@ from sqlglot import exp from sqlglot.dialects.dialect import Dialect, Dialects from sqlglot.errors import ParseError +from sqlglot.optimizer.pushdown_predicates import pushdown_predicates from sqlglot.optimizer.scope import Scope, ScopeType, traverse_scope from superset.exceptions import SupersetParseError +from superset.sql.dialects.firebolt import Firebolt logger = logging.getLogger(__name__) @@ -60,7 +63,7 @@ # "elasticsearch": ??? # "exa": ??? # "firebird": ??? - # "firebolt": ??? + "firebolt": Firebolt, "gsheets": Dialects.SQLITE, "hana": Dialects.POSTGRES, "hive": Dialects.HIVE, @@ -79,7 +82,7 @@ "presto": Dialects.PRESTO, "pydoris": Dialects.DORIS, "redshift": Dialects.REDSHIFT, - # "risingwave": ??? + "risingwave": Dialects.RISINGWAVE, # "rockset": ??? "shillelagh": Dialects.SQLITE, "snowflake": Dialects.SNOWFLAKE, @@ -227,6 +230,12 @@ def is_mutating(self) -> bool: """ raise NotImplementedError() + def optimize(self) -> BaseSQLStatement[InternalRepresentation]: + """ + Return optimized statement. + """ + raise NotImplementedError() + def __str__(self) -> str: return self.format() @@ -431,6 +440,19 @@ def get_settings(self) -> dict[str, str | bool]: for eq in set_item.find_all(exp.EQ) } + def optimize(self) -> SQLStatement: + """ + Return optimized statement. + """ + # only optimize statements that have a custom dialect + if not self._dialect: + return SQLStatement(self._sql, self.engine, self._parsed.copy()) + + optimized = pushdown_predicates(self._parsed, dialect=self._dialect) + sql = optimized.sql(dialect=self._dialect) + + return SQLStatement(sql, self.engine, optimized) + class KQLSplitState(enum.Enum): """ @@ -589,6 +611,14 @@ def is_mutating(self) -> bool: """ return self._parsed.startswith(".") and not self._parsed.startswith(".show") + def optimize(self) -> KustoKQLStatement: + """ + Return optimized statement. + + Kusto KQL doesn't support optimization, so this method is a no-op. + """ + return KustoKQLStatement(self._sql, self.engine, self._parsed) + class SQLScript: """ @@ -643,6 +673,17 @@ def has_mutation(self) -> bool: """ return any(statement.is_mutating() for statement in self.statements) + def optimize(self) -> SQLScript: + """ + Return optimized script. + """ + script = copy.deepcopy(self) + script.statements = [ # type: ignore + statement.optimize() for statement in self.statements + ] + + return script + def extract_tables_from_statement( statement: exp.Expression, diff --git a/superset/sqllab/permalink/__init__.py b/superset/sqllab/permalink/__init__.py new file mode 100644 index 0000000000000..13a83393a9124 --- /dev/null +++ b/superset/sqllab/permalink/__init__.py @@ -0,0 +1,16 @@ +# 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. diff --git a/superset/sqllab/permalink/api.py b/superset/sqllab/permalink/api.py new file mode 100644 index 0000000000000..c86fb99a5edb3 --- /dev/null +++ b/superset/sqllab/permalink/api.py @@ -0,0 +1,144 @@ +# 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 logging + +from flask import request, Response +from flask_appbuilder.api import expose, protect, safe +from marshmallow import ValidationError + +from superset.commands.sql_lab.permalink.create import CreateSqlLabPermalinkCommand +from superset.commands.sql_lab.permalink.get import GetSqlLabPermalinkCommand +from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP +from superset.extensions import event_logger +from superset.key_value.exceptions import KeyValueAccessDeniedError +from superset.sqllab.permalink.exceptions import SqlLabPermalinkInvalidStateError +from superset.sqllab.permalink.schemas import SqlLabPermalinkSchema +from superset.views.base_api import BaseSupersetApi, requires_json, statsd_metrics + +logger = logging.getLogger(__name__) + + +class SqlLabPermalinkRestApi(BaseSupersetApi): + add_model_schema = SqlLabPermalinkSchema() + method_permission_name = MODEL_API_RW_METHOD_PERMISSION_MAP + allow_browser_login = True + class_permission_name = "SqlLabPermalinkRestApi" + resource_name = "sqllab" + openapi_spec_tag = "SQL Lab Permanent Link" + openapi_spec_component_schemas = (SqlLabPermalinkSchema,) + + @expose("/permalink", methods=("POST",)) + @protect() + @safe + @statsd_metrics + @event_logger.log_this_with_context( + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.post", + log_to_statsd=False, + ) + @requires_json + def post(self) -> Response: + """Create a new permanent link for SQL Lab editor + --- + post: + summary: Create a new permanent link + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExplorePermalinkStateSchema' + responses: + 201: + description: The permanent link was stored successfully. + content: + application/json: + schema: + type: object + properties: + key: + type: string + description: The key to retrieve the permanent link data. + url: + type: string + description: permanent link. + 400: + $ref: '#/components/responses/400' + 401: + $ref: '#/components/responses/401' + 422: + $ref: '#/components/responses/422' + 500: + $ref: '#/components/responses/500' + """ + try: + state = self.add_model_schema.load(request.json) + key = CreateSqlLabPermalinkCommand(state=state).run() + http_origin = request.headers.environ.get("HTTP_ORIGIN") + url = f"{http_origin}/sqllab/p/{key}" + return self.response(201, key=key, url=url) + except ValidationError as ex: + return self.response(400, message=ex.messages) + except KeyValueAccessDeniedError as ex: + return self.response(403, message=str(ex)) + + @expose("/permalink/", methods=("GET",)) + @protect() + @safe + @statsd_metrics + @event_logger.log_this_with_context( + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.get", + log_to_statsd=False, + ) + def get(self, key: str) -> Response: + """Get chart's permanent link state. + --- + get: + summary: Get chart's permanent link state + parameters: + - in: path + schema: + type: string + name: key + responses: + 200: + description: Returns the stored form_data. + content: + application/json: + schema: + type: object + properties: + state: + type: object + description: The stored state + 400: + $ref: '#/components/responses/400' + 401: + $ref: '#/components/responses/401' + 404: + $ref: '#/components/responses/404' + 422: + $ref: '#/components/responses/422' + 500: + $ref: '#/components/responses/500' + """ + try: + value = GetSqlLabPermalinkCommand(key=key).run() + if not value: + return self.response_404() + return self.response(200, **value) + except SqlLabPermalinkInvalidStateError as ex: + return self.response(400, message=str(ex)) diff --git a/superset/sqllab/permalink/exceptions.py b/superset/sqllab/permalink/exceptions.py new file mode 100644 index 0000000000000..5d8dd7a9f4332 --- /dev/null +++ b/superset/sqllab/permalink/exceptions.py @@ -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. +from flask_babel import lazy_gettext as _ + +from superset.commands.exceptions import CommandException, CreateFailedError + + +class SqlLabPermalinkInvalidStateError(CreateFailedError): + message = _("Invalid state.") + + +class SqlLabPermalinkCreateFailedError(CreateFailedError): + message = _("An error occurred while creating the copy link.") + + +class SqlLabPermalinkGetFailedError(CommandException): + message = _("An error occurred while accessing the copy link.") diff --git a/superset/sqllab/permalink/schemas.py b/superset/sqllab/permalink/schemas.py new file mode 100644 index 0000000000000..d89c2b5796c36 --- /dev/null +++ b/superset/sqllab/permalink/schemas.py @@ -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. +from marshmallow import fields, Schema + + +class SqlLabPermalinkSchema(Schema): + autorun = fields.Boolean() + dbId = fields.Integer( # noqa: N815 + required=True, + allow_none=False, + metadata={"description": "The id of the database"}, + ) + name = fields.String( + required=True, + allow_none=False, + metadata={"description": "The label of the editor tab"}, + ) + schema = fields.String( + required=False, + allow_none=True, + metadata={"description": "The schema name of the query"}, + ) + sql = fields.String( + required=True, + allow_none=False, + metadata={"description": "SQL query text"}, + ) + templateParams = fields.String( # noqa: N815 + required=False, + allow_none=True, + metadata={"description": "stringfied JSON string for template parameters"}, + ) diff --git a/superset/sqllab/permalink/types.py b/superset/sqllab/permalink/types.py new file mode 100644 index 0000000000000..adc127da1117a --- /dev/null +++ b/superset/sqllab/permalink/types.py @@ -0,0 +1,26 @@ +# 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. +from typing import Optional, TypedDict + + +class SqlLabPermalinkValue(TypedDict): + dbId: int + name: str + schema: Optional[str] + sql: str + autorun: bool + templateParams: Optional[str] diff --git a/superset/static/assets/.gitkeep b/superset/static/assets/.gitkeep new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/superset/static/uploads/.gitkeep b/superset/static/uploads/.gitkeep new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/superset/utils/date_parser.py b/superset/utils/date_parser.py index 63589601a72f9..cd404a3fb4733 100644 --- a/superset/utils/date_parser.py +++ b/superset/utils/date_parser.py @@ -143,6 +143,174 @@ def parse_past_timedelta( ) +def get_relative_base(unit: str, relative_start: str | None = None) -> str: + """ + Determines the relative base (`now` or `today`) based on the granularity of the unit + and an optional user-provided base expression. This is used as the base for all + queries parsed from `time_range_lookup`. + + Args: + unit (str): The time unit (e.g., "second", "minute", "hour", "day", etc.). + relative_start (datetime | None): Optional user-provided base time. + + Returns: + datetime: The base time (`now`, `today`, or user-provided). + """ + if relative_start is not None: + return relative_start + + granular_units = {"second", "minute", "hour"} + broad_units = {"day", "week", "month", "quarter", "year"} + + if unit.lower() in granular_units: + return "now" + elif unit.lower() in broad_units: + return "today" + raise ValueError(f"Unknown unit: {unit}") + + +def handle_start_of(base_expression: str, unit: str) -> str: + """ + Generates a datetime expression for the start of a given unit (e.g., start of month, + start of year). + This function is used to handle queries matching the first regex in + `time_range_lookup`. + + Args: + base_expression (str): The base datetime expression (e.g., "DATETIME('now')"), + provided by `get_relative_base`. + unit (str): The granularity to calculate the start for (e.g., "year", + "month", "week"), + extracted from the regex. + + Returns: + str: The resulting expression for the start of the specified unit. + + Raises: + ValueError: If the unit is not one of the valid options. + + Relation to `time_range_lookup`: + - Handles the "start of" or "beginning of" modifiers in the first regex pattern. + - Example: "start of this month" → `DATETRUNC(DATETIME('today'), month)`. + """ + valid_units = {"year", "quarter", "month", "week", "day"} + if unit in valid_units: + return f"DATETRUNC({base_expression}, {unit})" + raise ValueError(f"Invalid unit for 'start of': {unit}") + + +def handle_end_of(base_expression: str, unit: str) -> str: + """ + Generates a datetime expression for the end of a given unit (e.g., end of month, + end of year). + This function is used to handle queries matching the first regex in + `time_range_lookup`. + + Args: + base_expression (str): The base datetime expression (e.g., "DATETIME('now')"), + provided by `get_relative_base`. + unit (str): The granularity to calculate the end for (e.g., "year", "month", + "week"), extracted from the regex. + + Returns: + str: The resulting expression for the end of the specified unit. + + Raises: + ValueError: If the unit is not one of the valid options. + + Relation to `time_range_lookup`: + - Handles the "end of" modifier in the first regex pattern. + - Example: "end of last month" → `LASTDAY(DATETIME('today'), month)`. + """ + valid_units = {"year", "quarter", "month", "week", "day"} + if unit in valid_units: + return f"LASTDAY({base_expression}, {unit})" + raise ValueError(f"Invalid unit for 'end of': {unit}") + + +def handle_modifier_and_unit( + modifier: str, scope: str, delta: str, unit: str, relative_base: str +) -> str: + """ + Generates a datetime expression based on a modifier, scope, delta, unit, + and relative base. + This function handles queries matching the first regex pattern in + `time_range_lookup`. + + Args: + modifier (str): Specifies the operation (e.g., "start of", "end of"). + Extracted from the regex to determine whether to calculate the start or end. + scope (str): The time scope (e.g., "this", "last", "next", "prior"), + extracted from the regex. + delta (str): The numeric delta value (e.g., "1", "2"), extracted from the regex. + unit (str): The granularity (e.g., "day", "month", "year"), extracted from + the regex. + relative_base (str): The base datetime expression (e.g., "now" or "today"), + determined by `get_relative_base`. + + Returns: + str: The resulting datetime expression. + + Raises: + ValueError: If the modifier is invalid. + + Relation to `time_range_lookup`: + - Processes queries like "start of this month" or "end of prior 2 years". + - Example: "start of this month" → `DATETRUNC(DATETIME('today'), month)`. + + Example: + >>> handle_modifier_and_unit("start of", "this", "", "month", "today") + "DATETRUNC(DATETIME('today'), month)" + + >>> handle_modifier_and_unit("end of", "last", "1", "year", "today") + "LASTDAY(DATEADD(DATETIME('today'), -1, year), year)" + """ + base_expression = handle_scope_and_unit(scope, delta, unit, relative_base) + + if modifier.lower() in ["start of", "beginning of"]: + return handle_start_of(base_expression, unit.lower()) + elif modifier.lower() == "end of": + return handle_end_of(base_expression, unit.lower()) + else: + raise ValueError(f"Unknown modifier: {modifier}") + + +def handle_scope_and_unit(scope: str, delta: str, unit: str, relative_base: str) -> str: + """ + Generates a datetime expression based on the scope, delta, unit, and relative base. + This function handles queries matching the second regex pattern in + `time_range_lookup`. + + Args: + scope (str): The time scope (e.g., "this", "last", "next", "prior"), + extracted from the regex. + delta (str): The numeric delta value (e.g., "1", "2"), extracted from the regex. + unit (str): The granularity (e.g., "second", "minute", "hour", "day"), + extracted from the regex. + relative_base (str): The base datetime expression (e.g., "now" or "today"), + determined by `get_relative_base`. + + Returns: + str: The resulting datetime expression. + + Raises: + ValueError: If the scope is invalid. + + Relation to `time_range_lookup`: + - Processes queries like "last 2 weeks" or "this month". + - Example: "last 2 weeks" → `DATEADD(DATETIME('today'), -2, week)`. + """ + _delta = int(delta) if delta else 1 + if scope.lower() == "this": + return f"DATETIME('{relative_base}')" + elif scope.lower() in ["last", "prior"]: + return f"DATEADD(DATETIME('{relative_base}'), -{_delta}, {unit})" + elif scope.lower() == "next": + return f"DATEADD(DATETIME('{relative_base}'), {_delta}, {unit})" + else: + raise ValueError(f"Invalid scope: {scope}") + + def get_since_until( # pylint: disable=too-many-arguments,too-many-locals,too-many-branches,too-many-statements # noqa: C901 time_range: str | None = None, since: str | None = None, @@ -241,21 +409,28 @@ def get_since_until( # pylint: disable=too-many-arguments,too-many-locals,too-m if time_range and separator in time_range: time_range_lookup = [ ( - r"^last\s+(day|week|month|quarter|year)$", - lambda unit: f"DATEADD(DATETIME('{_relative_start}'), -1, {unit})", - ), - ( - r"^last\s+([0-9]+)\s+(second|minute|hour|day|week|month|year)s?$", - lambda delta, - unit: f"DATEADD(DATETIME('{_relative_start}'), -{int(delta)}, {unit})", # pylint: disable=line-too-long,useless-suppression + r"^(start of|beginning of|end of)\s+" + r"(this|last|next|prior)\s+" + r"([0-9]+)?\s*" + r"(day|week|month|quarter|year)s?$", # Matches phrases like "start of next month" # pylint: disable=line-too-long,useless-suppression # noqa: E501 + lambda modifier, scope, delta, unit: handle_modifier_and_unit( + modifier, + scope, + delta, + unit, + get_relative_base(unit, relative_start), + ), ), ( - r"^next\s+([0-9]+)\s+(second|minute|hour|day|week|month|year)s?$", - lambda delta, - unit: f"DATEADD(DATETIME('{_relative_end}'), {int(delta)}, {unit})", # pylint: disable=line-too-long,useless-suppression + r"^(this|last|next|prior)\s+" + r"([0-9]+)?\s*" + r"(second|minute|day|week|month|quarter|year)s?$", # Matches "next 5 days" or "last 2 weeks" # pylint: disable=line-too-long,useless-suppression # noqa: E501 + lambda scope, delta, unit: handle_scope_and_unit( + scope, delta, unit, get_relative_base(unit, relative_start) + ), ), ( - r"^(DATETIME.*|DATEADD.*|DATETRUNC.*|LASTDAY.*|HOLIDAY.*)$", + r"^(DATETIME.*|DATEADD.*|DATETRUNC.*|LASTDAY.*|HOLIDAY.*)$", # Matches date-related keywords # pylint: disable=line-too-long,useless-suppression # noqa: E501 lambda text: text, ), ] diff --git a/superset/utils/encrypt.py b/superset/utils/encrypt.py index 85d45f13a22de..53401a70602d6 100644 --- a/superset/utils/encrypt.py +++ b/superset/utils/encrypt.py @@ -22,7 +22,12 @@ from flask_babel import lazy_gettext as _ from sqlalchemy import text, TypeDecorator from sqlalchemy.engine import Connection, Dialect, Row -from sqlalchemy_utils import EncryptedType +from sqlalchemy_utils import EncryptedType as SqlaEncryptedType + + +class EncryptedType(SqlaEncryptedType): + cache_ok = True + ENC_ADAPTER_TAG_ATTR_NAME = "__created_by_enc_field_adapter__" logger = logging.getLogger(__name__) diff --git a/superset/utils/excel.py b/superset/utils/excel.py index 602549975f113..d34446832a8cc 100644 --- a/superset/utils/excel.py +++ b/superset/utils/excel.py @@ -56,7 +56,7 @@ def df_to_excel(df: pd.DataFrame, **kwargs: Any) -> Any: def apply_column_types( df: pd.DataFrame, column_types: list[GenericDataType] ) -> pd.DataFrame: - for column, column_type in zip(df.columns, column_types): + for column, column_type in zip(df.columns, column_types, strict=False): if column_type == GenericDataType.NUMERIC: try: df[column] = pd.to_numeric(df[column]) diff --git a/superset/utils/mock_data.py b/superset/utils/mock_data.py index 88c9d5a57e5f2..b156273dc0b7a 100644 --- a/superset/utils/mock_data.py +++ b/superset/utils/mock_data.py @@ -221,8 +221,11 @@ def get_column_objects(columns: list[ColumnInfo]) -> list[Column]: def generate_data(columns: list[ColumnInfo], num_rows: int) -> list[dict[str, Any]]: keys = [column["name"] for column in columns] return [ - dict(zip(keys, row)) - for row in zip(*[generate_column_data(column, num_rows) for column in columns]) + dict(zip(keys, row, strict=False)) + for row in zip( + *[generate_column_data(column, num_rows) for column in columns], + strict=False, + ) ] diff --git a/superset/utils/pandas_postprocessing/compare.py b/superset/utils/pandas_postprocessing/compare.py index 64442280b2af2..22b345bb31170 100644 --- a/superset/utils/pandas_postprocessing/compare.py +++ b/superset/utils/pandas_postprocessing/compare.py @@ -59,7 +59,7 @@ def compare( # pylint: disable=too-many-arguments if len(source_columns) == 0: return df - for s_col, c_col in zip(source_columns, compare_columns): + for s_col, c_col in zip(source_columns, compare_columns, strict=False): s_df = df.loc[:, [s_col]] s_df.rename(columns={s_col: "__intermediate"}, inplace=True) c_df = df.loc[:, [c_col]] diff --git a/superset/utils/pandas_postprocessing/geography.py b/superset/utils/pandas_postprocessing/geography.py index 79046cb71a1b2..c5f46cd490ccc 100644 --- a/superset/utils/pandas_postprocessing/geography.py +++ b/superset/utils/pandas_postprocessing/geography.py @@ -40,7 +40,7 @@ def geohash_decode( try: lonlat_df = DataFrame() lonlat_df["latitude"], lonlat_df["longitude"] = zip( - *df[geohash].apply(geohash_lib.decode) + *df[geohash].apply(geohash_lib.decode), strict=False ) return _append_columns( df, lonlat_df, {"latitude": latitude, "longitude": longitude} @@ -109,7 +109,7 @@ def _parse_location(location: str) -> tuple[float, float, float]: geodetic_df["latitude"], geodetic_df["longitude"], geodetic_df["altitude"], - ) = zip(*df[geodetic].apply(_parse_location)) + ) = zip(*df[geodetic].apply(_parse_location), strict=False) columns = {"latitude": latitude, "longitude": longitude} if altitude: columns["altitude"] = altitude diff --git a/superset/utils/pandas_postprocessing/histogram.py b/superset/utils/pandas_postprocessing/histogram.py index 74fc68e227807..f55f0d0762673 100644 --- a/superset/utils/pandas_postprocessing/histogram.py +++ b/superset/utils/pandas_postprocessing/histogram.py @@ -71,7 +71,7 @@ def hist_values(series: Series) -> np.ndarray: if len(groupby) == 0: # without grouping - hist_dict = dict(zip(bin_edges_str, hist_values(df[column]))) + hist_dict = dict(zip(bin_edges_str, hist_values(df[column]), strict=False)) histogram_df = DataFrame(hist_dict, index=[0]) else: # with grouping diff --git a/superset/utils/webdriver.py b/superset/utils/webdriver.py index 727f721308acb..c8e46581ed861 100644 --- a/superset/utils/webdriver.py +++ b/superset/utils/webdriver.py @@ -21,9 +21,11 @@ from abc import ABC, abstractmethod from enum import Enum from time import sleep -from typing import Any, TYPE_CHECKING +from typing import TYPE_CHECKING from flask import current_app +from packaging import version +from selenium import __version__ as selenium_version from selenium.common.exceptions import ( StaleElementReferenceException, TimeoutException, @@ -31,6 +33,7 @@ ) from selenium.webdriver import chrome, firefox, FirefoxProfile from selenium.webdriver.common.by import By +from selenium.webdriver.common.service import Service from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.support import expected_conditions as EC # noqa: N812 from selenium.webdriver.support.ui import WebDriverWait @@ -246,13 +249,16 @@ class WebDriverSelenium(WebDriverProxy): def create(self) -> WebDriver: pixel_density = current_app.config["WEBDRIVER_WINDOW"].get("pixel_density", 1) if self._driver_type == "firefox": - driver_class = firefox.webdriver.WebDriver + driver_class: type[WebDriver] = firefox.webdriver.WebDriver + service_class: type[Service] = firefox.service.Service options = firefox.options.Options() profile = FirefoxProfile() profile.set_preference("layout.css.devPixelsPerPx", str(pixel_density)) - kwargs: dict[Any, Any] = {"options": options, "firefox_profile": profile} + options.profile = profile + kwargs = {"options": options} elif self._driver_type == "chrome": driver_class = chrome.webdriver.WebDriver + service_class = chrome.service.Service options = chrome.options.Options() options.add_argument(f"--force-device-scale-factor={pixel_density}") options.add_argument(f"--window-size={self._window[0]},{self._window[1]}") @@ -261,15 +267,41 @@ def create(self) -> WebDriver: raise Exception( # pylint: disable=broad-exception-raised f"Webdriver name ({self._driver_type}) not supported" ) - # Prepare args for the webdriver init - # Add additional configured options - for arg in current_app.config["WEBDRIVER_OPTION_ARGS"]: + # Prepare args for the webdriver init + for arg in list(current_app.config["WEBDRIVER_OPTION_ARGS"]): options.add_argument(arg) - kwargs.update(current_app.config["WEBDRIVER_CONFIGURATION"]) - logger.debug("Init selenium driver") + # Add additional configured webdriver options + webdriver_conf = dict(current_app.config["WEBDRIVER_CONFIGURATION"]) + + if version.parse(selenium_version) < version.parse("4.10.0"): + kwargs |= webdriver_conf + else: + driver_opts = dict( + webdriver_conf.get("options", {"capabilities": {}, "preferences": {}}) + ) + driver_srv = dict( + webdriver_conf.get( + "service", + { + "log_output": "/dev/null", + "service_args": [], + "port": 0, + "env": {}, + }, + ) + ) + for name, value in driver_opts.get("capabilities", {}).items(): + options.set_capability(name, value) + if hasattr(options, "profile"): + for name, value in driver_opts.get("preferences", {}).items(): + options.profile.set_preference(str(name), value) + kwargs |= { + "service": service_class(**driver_srv), + } + logger.debug("Init selenium driver") return driver_class(**kwargs) def auth(self, user: User) -> WebDriver: diff --git a/superset/views/base_api.py b/superset/views/base_api.py index 8240481adaa61..682e8f8697130 100644 --- a/superset/views/base_api.py +++ b/superset/views/base_api.py @@ -31,6 +31,7 @@ from sqlalchemy import and_, distinct, func from sqlalchemy.orm.query import Query +from superset import is_feature_enabled from superset.exceptions import InvalidPayloadFormatError from superset.extensions import db, event_logger, security_manager, stats_logger_manager from superset.models.core import FavStar @@ -130,6 +131,29 @@ def wraps(self: BaseSupersetApiMixin, *args: Any, **kwargs: Any) -> Response: return functools.update_wrapper(wraps, f) +def validate_feature_flags( + feature_flags: list[str], +) -> Callable[[Callable[..., Response]], Callable[..., Response]]: + """ + A decorator to check if all given feature flags are enabled. + + :param feature_flags: List of feature flag names to be checked. + """ + + def decorate(f: Callable[..., Response]) -> Callable[..., Response]: + @functools.wraps(f) + def wrapper( + self: BaseSupersetModelRestApi, *args: Any, **kwargs: Any + ) -> Response: + if not all(is_feature_enabled(flag) for flag in feature_flags): + return self.response_404() + return f(self, *args, **kwargs) + + return wrapper + + return decorate + + class RelatedFieldFilter: # data class to specify what filter to use on a /related endpoint # pylint: disable=too-few-public-methods diff --git a/superset/views/key_value.py b/superset/views/key_value.py deleted file mode 100644 index 69a5314c5fb57..0000000000000 --- a/superset/views/key_value.py +++ /dev/null @@ -1,67 +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. -from flask import request, Response -from flask_appbuilder import expose -from flask_appbuilder.hooks import before_request -from flask_appbuilder.security.decorators import has_access_api -from werkzeug.exceptions import NotFound - -from superset import db, event_logger, is_feature_enabled -from superset.models import core as models -from superset.superset_typing import FlaskResponse -from superset.utils import core as utils, json -from superset.views.base import BaseSupersetView, deprecated, json_error_response - - -class KV(BaseSupersetView): - """Used for storing and retrieving key value pairs""" - - @staticmethod - def is_enabled() -> bool: - return is_feature_enabled("KV_STORE") - - @before_request - def ensure_enabled(self) -> None: - if not self.is_enabled(): - raise NotFound() - - @event_logger.log_this - @has_access_api - @expose("/store/", methods=("POST",)) - @deprecated(eol_version="5.0.0") - def store(self) -> FlaskResponse: - try: - value = request.form.get("data") - obj = models.KeyValue(value=value) - db.session.add(obj) - db.session.commit() # pylint: disable=consider-using-transaction - except Exception as ex: # pylint: disable=broad-except - return json_error_response(utils.error_msg_from_exception(ex)) - return Response(json.dumps({"id": obj.id}), status=200) - - @event_logger.log_this - @has_access_api - @expose("//", methods=("GET",)) - @deprecated(eol_version="5.0.0") - def get_value(self, key_id: int) -> FlaskResponse: - try: - kv = db.session.query(models.KeyValue).filter_by(id=key_id).scalar() - if not kv: - return Response(status=404, content_type="text/plain") - except Exception as ex: # pylint: disable=broad-except - return json_error_response(utils.error_msg_from_exception(ex)) - return Response(kv.value, status=200, content_type="text/plain") diff --git a/superset/views/sqllab.py b/superset/views/sqllab.py index 5677237c4823c..b34a3af7d4e4b 100644 --- a/superset/views/sqllab.py +++ b/superset/views/sqllab.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. import contextlib +from typing import Any from flask import request from flask_appbuilder import permission_name @@ -36,10 +37,11 @@ class SqllabView(BaseSupersetView): method_permission_name = MODEL_API_RW_METHOD_PERMISSION_MAP @expose("/", methods=["GET", "POST"]) + @expose("/p//", methods=["GET"]) @has_access @permission_name("read") @event_logger.log_this - def root(self) -> FlaskResponse: + def root(self, **kwargs: Any) -> FlaskResponse: payload = {} if form_data := request.form.get("form_data"): with contextlib.suppress(json.JSONDecodeError): diff --git a/superset/viz.py b/superset/viz.py index a8f9e7664b104..a42ce94203091 100644 --- a/superset/viz.py +++ b/superset/viz.py @@ -27,7 +27,6 @@ import dataclasses import logging import math -import re from collections import defaultdict, OrderedDict from datetime import datetime, timedelta from itertools import product @@ -46,7 +45,6 @@ from superset import app from superset.common.db_query_status import QueryStatus -from superset.constants import NULL_STRING from superset.errors import ErrorLevel, SupersetError, SupersetErrorType from superset.exceptions import ( CacheLoadError, @@ -632,9 +630,11 @@ def get_df_payload( # pylint: disable=too-many-statements # noqa: C901 "stacktrace": stacktrace, "rowcount": len(df.index) if df is not None else 0, "colnames": list(df.columns) if df is not None else None, - "coltypes": utils.extract_dataframe_dtypes(df, self.datasource) - if df is not None - else None, + "coltypes": ( + utils.extract_dataframe_dtypes(df, self.datasource) + if df is not None + else None + ), } @staticmethod @@ -1121,14 +1121,6 @@ def get_data(self, df: pd.DataFrame) -> VizData: return chart_data -class NVD3TimeSeriesBarViz(NVD3TimeSeriesViz): - """A bar chart where the x axis is time""" - - viz_type = "bar" - sort_series = True - verbose_name = _("Time Series - Bar Chart") - - class NVD3TimePivotViz(NVD3TimeSeriesViz): """Time Series - Periodicity Pivot""" @@ -1184,235 +1176,6 @@ class NVD3CompareTimeSeriesViz(NVD3TimeSeriesViz): verbose_name = _("Time Series - Percent Change") -class NVD3TimeSeriesStackedViz(NVD3TimeSeriesViz): - """A rich stack area chart""" - - viz_type = "area" - verbose_name = _("Time Series - Stacked") - sort_series = True - pivot_fill_value = 0 - - -class HistogramViz(BaseViz): - """Histogram""" - - viz_type = "histogram" - verbose_name = _("Histogram") - is_timeseries = False - - @deprecated(deprecated_in="3.0") - def query_obj(self) -> QueryObjectDict: - """Returns the query object for this visualization""" - query_obj = super().query_obj() - numeric_columns = self.form_data.get("all_columns_x") - if numeric_columns is None: - raise QueryObjectValidationError( - _("Must have at least one numeric column specified") - ) - self.columns = ( # pylint: disable=attribute-defined-outside-init - numeric_columns - ) - query_obj["columns"] = numeric_columns + self.groupby - # override groupby entry to avoid aggregation - query_obj["groupby"] = None - query_obj["metrics"] = None - return query_obj - - @deprecated(deprecated_in="3.0") - def labelify(self, keys: list[str] | str, column: str) -> str: - if isinstance(keys, str): - keys = [keys] - # removing undesirable characters - labels = [re.sub(r"\W+", r"_", k) for k in keys] - if len(self.columns) > 1 or not self.groupby: - # Only show numeric column in label if there are many - labels = [column] + labels - return "__".join(labels) - - @deprecated(deprecated_in="3.0") - def get_data(self, df: pd.DataFrame) -> VizData: - """Returns the chart data""" - if df.empty: - return None - - chart_data = [] - if len(self.groupby) > 0: - groups = df.groupby(get_column_names(self.groupby)) - else: - groups = [((), df)] - for keys, data in groups: - chart_data.extend( - [ - { - "key": self.labelify(keys, get_column_name(column)), - "values": data[get_column_name(column)].tolist(), - } - for column in self.columns - ] - ) - return chart_data - - -class DistributionBarViz(BaseViz): - """A good old bar chart""" - - viz_type = "dist_bar" - verbose_name = _("Distribution - Bar Chart") - is_timeseries = False - - @deprecated(deprecated_in="3.0") - def query_obj(self) -> QueryObjectDict: - query_obj = super().query_obj() - if len(query_obj["groupby"]) < len(self.form_data.get("groupby") or []) + len( - self.form_data.get("columns") or [] - ): - raise QueryObjectValidationError( - _("Can't have overlap between Series and Breakdowns") - ) - if not self.form_data.get("metrics"): - raise QueryObjectValidationError(_("Pick at least one metric")) - if not self.form_data.get("groupby"): - raise QueryObjectValidationError(_("Pick at least one field for [Series]")) - - if sort_by := self.form_data.get("timeseries_limit_metric"): - sort_by_label = utils.get_metric_name(sort_by) - if sort_by_label not in utils.get_metric_names(query_obj["metrics"]): - query_obj["metrics"].append(sort_by) - query_obj["orderby"] = [ - (sort_by, not self.form_data.get("order_desc", True)) - ] - elif query_obj["metrics"]: - # Legacy behavior of sorting by first metric by default - first_metric = query_obj["metrics"][0] - query_obj["orderby"] = [ - (first_metric, not self.form_data.get("order_desc", True)) - ] - - return query_obj - - @deprecated(deprecated_in="3.0") - def get_data(self, df: pd.DataFrame) -> VizData: # pylint: disable=too-many-locals - if df.empty: - return None - - metrics = self.metric_labels - columns = get_column_names(self.form_data.get("columns")) - groupby = get_column_names(self.groupby) - - # pandas will throw away nulls when grouping/pivoting, - # so we substitute NULL_STRING for any nulls in the necessary columns - filled_cols = groupby + columns - df = df.copy() - df[filled_cols] = df[filled_cols].fillna(value=NULL_STRING) - - sortby = utils.get_metric_name( - self.form_data.get("timeseries_limit_metric") or metrics[0] - ) - row = df.groupby(groupby)[sortby].sum().copy() - is_asc = not self.form_data.get("order_desc") - row.sort_values(ascending=is_asc, inplace=True) - pt = df.pivot_table(index=groupby, columns=columns, values=metrics) - if self.form_data.get("contribution"): - pt = pt.T - pt = (pt / pt.sum()).T - pt = pt.reindex(row.index) - - # Re-order the columns adhering to the metric ordering. - pt = pt[metrics] - chart_data = [] - for name, ys in pt.items(): - if pt[name].dtype.kind not in "biufc" or name in groupby: - continue - if isinstance(name, str): - series_title = name - else: - offset = 0 if len(metrics) > 1 else 1 - series_title = ", ".join([str(s) for s in name[offset:]]) - values = [] - for i, v in ys.items(): - x = i - if isinstance(x, (tuple, list)): - x = ", ".join([str(s) for s in x]) - else: - x = str(x) - values.append({"x": x, "y": v}) - chart_data.append({"key": series_title, "values": values}) - return chart_data - - -class SankeyViz(BaseViz): - """A Sankey diagram that requires a parent-child dataset""" - - viz_type = "sankey" - verbose_name = _("Sankey") - is_timeseries = False - credits = 'd3-sankey on npm' - - @deprecated(deprecated_in="3.0") - def query_obj(self) -> QueryObjectDict: - query_obj = super().query_obj() - if len(query_obj["groupby"]) != 2: - raise QueryObjectValidationError( - _("Pick exactly 2 columns as [Source / Target]") - ) - query_obj["metrics"] = [self.form_data["metric"]] - if self.form_data.get("sort_by_metric", False): - query_obj["orderby"] = [(query_obj["metrics"][0], False)] - return query_obj - - @deprecated(deprecated_in="3.0") - def get_data(self, df: pd.DataFrame) -> VizData: - if df.empty: - return None - source, target = get_column_names(self.groupby) - (value,) = self.metric_labels - df.rename( - columns={ - source: "source", - target: "target", - value: "value", - }, - inplace=True, - ) - df["source"] = df["source"].astype(str) - df["target"] = df["target"].astype(str) - recs = df.to_dict(orient="records") - - hierarchy: dict[str, set[str]] = defaultdict(set) - for row in recs: - hierarchy[row["source"]].add(row["target"]) - - @deprecated(deprecated_in="3.0") - def find_cycle(graph: dict[str, set[str]]) -> tuple[str, str] | None: - """Whether there's a cycle in a directed graph""" - path = set() - - @deprecated(deprecated_in="3.0") - def visit(vertex: str) -> tuple[str, str] | None: - path.add(vertex) - for neighbour in graph.get(vertex, ()): - if neighbour in path or visit(neighbour): - return (vertex, neighbour) - path.remove(vertex) - return None - - for vertex in graph: - cycle = visit(vertex) - if cycle: - return cycle - return None - - cycle = find_cycle(hierarchy) - if cycle: - raise QueryObjectValidationError( - _( - "There's a loop in your Sankey, please provide a tree. " - "Here's a faulty link: {}" - ).format(cycle) - ) - return recs - - class ChordViz(BaseViz): """A Chord diagram""" @@ -1586,65 +1349,6 @@ def get_data(self, df: pd.DataFrame) -> VizData: return df.to_dict(orient="records") -class HeatmapViz(BaseViz): - """A nice heatmap visualization that support high density through canvas""" - - viz_type = "heatmap" - verbose_name = _("Heatmap") - is_timeseries = False - credits = ( - 'inspired from mbostock @' - "bl.ocks.org" - ) - - @deprecated(deprecated_in="3.0") - def query_obj(self) -> QueryObjectDict: - query_obj = super().query_obj() - query_obj["metrics"] = [self.form_data.get("metric")] - query_obj["groupby"] = [ - self.form_data.get("all_columns_x"), - self.form_data.get("all_columns_y"), - ] - - if self.form_data.get("sort_by_metric", False): - query_obj["orderby"] = [(query_obj["metrics"][0], False)] - - return query_obj - - @deprecated(deprecated_in="3.0") - def get_data(self, df: pd.DataFrame) -> VizData: - if df.empty: - return None - - x = get_column_name(self.form_data.get("all_columns_x")) # type: ignore - y = get_column_name(self.form_data.get("all_columns_y")) # type: ignore - v = self.metric_labels[0] - if x == y: - df.columns = ["x", "y", "v"] - else: - df = df[[x, y, v]] - df.columns = ["x", "y", "v"] - norm = self.form_data.get("normalize_across") - overall = False - max_ = df.v.max() - min_ = df.v.min() - if norm == "heatmap": - overall = True - else: - gb = df.groupby(norm, group_keys=False) - if len(gb) <= 1: - overall = True - else: - df["perc"] = gb.apply( - lambda x: (x.v - x.v.min()) / (x.v.max() - x.v.min()) - ) - df["rank"] = gb.apply(lambda x: x.v.rank(pct=True)) - if overall: - df["perc"] = (df.v - min_) / (max_ - min_) - df["rank"] = df.v.rank(pct=True) - return {"records": df.to_dict(orient="records"), "extents": [min_, max_]} - - class HorizonViz(NVD3TimeSeriesViz): """Horizon chart @@ -1779,6 +1483,7 @@ def get_data(self, df: pd.DataFrame) -> VizData: df[self.form_data.get("all_columns_y")], metric_col, point_radius_col, + strict=False, ) ], } @@ -1902,6 +1607,7 @@ def process_spatial_data_obj(self, key: str, df: pd.DataFrame) -> pd.DataFrame: zip( pd.to_numeric(df[spatial.get("lonCol")], errors="coerce"), pd.to_numeric(df[spatial.get("latCol")], errors="coerce"), + strict=False, ) ) elif spatial.get("type") == "delimited": @@ -2041,11 +1747,13 @@ def get_metrics(self) -> list[str]: def get_properties(self, data: dict[str, Any]) -> dict[str, Any]: return { "metric": data.get(self.metric_label) if self.metric_label else None, - "radius": self.fixed_value - if self.fixed_value - else data.get(self.metric_label) - if self.metric_label - else None, + "radius": ( + self.fixed_value + if self.fixed_value + else data.get(self.metric_label) + if self.metric_label + else None + ), "cat_color": data.get(self.dim) if self.dim else None, "position": data.get("spatial"), DTTM_ALIAS: data.get(DTTM_ALIAS), diff --git a/tests/integration_tests/async_events/api_tests.py b/tests/integration_tests/async_events/api_tests.py index 80e9fa9b3b840..02852ab6fb85c 100644 --- a/tests/integration_tests/async_events/api_tests.py +++ b/tests/integration_tests/async_events/api_tests.py @@ -17,7 +17,7 @@ from typing import Any, Optional, Type from unittest import mock -import redis +import pytest from superset.async_events.cache_backend import ( RedisCacheBackend, @@ -30,6 +30,7 @@ from tests.integration_tests.test_app import app +@pytest.skip(reason="Needs to investigate this test", allow_module_level=True) class TestAsyncEventApi(SupersetTestCase): UUID = "943c920-32a5-412a-977d-b8e47d36f5a4" @@ -127,10 +128,6 @@ def test_events_redis_sentinel_cache_backend(self, mock_uuid4): RedisSentinelCacheBackend, self._test_events_logic ) - @mock.patch("uuid.uuid4", return_value=UUID) - def test_events_redis(self, mock_uuid4): - self.run_test_with_cache_backend(redis.Redis, self._test_events_logic) - def test_events_no_login(self): app._got_first_request = False async_query_manager.init_app(app) diff --git a/tests/integration_tests/cache_tests.py b/tests/integration_tests/cache_tests.py index 1356e32cd81f3..6857a7ccb6fc6 100644 --- a/tests/integration_tests/cache_tests.py +++ b/tests/integration_tests/cache_tests.py @@ -47,20 +47,29 @@ def test_no_data_cache(self): app.config["DATA_CACHE_CONFIG"] = {"CACHE_TYPE": "NullCache"} cache_manager.init_app(app) - slc = self.get_slice("Top 10 Girl Name Share") - json_endpoint = "/superset/explore_json/{}/{}/".format( - slc.datasource_type, slc.datasource_id - ) + slc = self.get_slice("Pivot Table v2") + + # Get chart metadata + metadata = self.get_json_resp(f"api/v1/chart/{slc.id}") + query_context = json.loads(metadata.get("result").get("query_context")) + query_context["form_data"] = slc.form_data + + # Request chart for the first time resp = self.get_json_resp( - json_endpoint, {"form_data": json.dumps(slc.viz.form_data)} + "api/v1/chart/data", + json_=query_context, ) + + # Request chart for the second time resp_from_cache = self.get_json_resp( - json_endpoint, {"form_data": json.dumps(slc.viz.form_data)} + "api/v1/chart/data", + json_=query_context, ) + # restore DATA_CACHE_CONFIG app.config["DATA_CACHE_CONFIG"] = data_cache_config - assert not resp["is_cached"] - assert not resp_from_cache["is_cached"] + assert resp.get("result")[0].get("cached_dttm") is None + assert resp_from_cache.get("result")[0].get("cached_dttm") is None @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") def test_slice_data_cache(self): @@ -74,30 +83,45 @@ def test_slice_data_cache(self): } cache_manager.init_app(app) - slc = self.get_slice("Top 10 Girl Name Share") - json_endpoint = "/superset/explore_json/{}/{}/".format( - slc.datasource_type, slc.datasource_id - ) + slc = self.get_slice("Pivot Table v2") + + # Get chart metadata + metadata = self.get_json_resp(f"api/v1/chart/{slc.id}") + query_context = json.loads(metadata.get("result").get("query_context")) + query_context["form_data"] = slc.form_data + + # Request chart for the first time resp = self.get_json_resp( - json_endpoint, {"form_data": json.dumps(slc.viz.form_data)} + "api/v1/chart/data", + json_=query_context, ) + + # Request chart for the second time resp_from_cache = self.get_json_resp( - json_endpoint, {"form_data": json.dumps(slc.viz.form_data)} + "api/v1/chart/data", + json_=query_context, ) - assert not resp["is_cached"] - assert resp_from_cache["is_cached"] + + result = resp.get("result")[0] + cached_result = resp_from_cache.get("result")[0] + + assert result.get("cached_dttm") is None + assert cached_result.get("cached_dttm") is not None + # should fallback to default cache timeout - assert resp_from_cache["cache_timeout"] == 10 - assert resp_from_cache["status"] == QueryStatus.SUCCESS - assert resp["data"] == resp_from_cache["data"] - assert resp["query"] == resp_from_cache["query"] + assert cached_result["cache_timeout"] == 10 + assert cached_result["status"] == QueryStatus.SUCCESS + assert result["data"] == cached_result["data"] + assert result["query"] == cached_result["query"] + # should exists in `data_cache` assert ( - cache_manager.data_cache.get(resp_from_cache["cache_key"])["query"] - == resp_from_cache["query"] + cache_manager.data_cache.get(cached_result["cache_key"])["query"] + == cached_result["query"] ) + # should not exists in `cache` - assert cache_manager.cache.get(resp_from_cache["cache_key"]) is None + assert cache_manager.cache.get(cached_result["cache_key"]) is None # reset cache config app.config["DATA_CACHE_CONFIG"] = data_cache_config diff --git a/tests/integration_tests/charts/api_tests.py b/tests/integration_tests/charts/api_tests.py index 78fc28a2ad459..5fd27bed0f864 100644 --- a/tests/integration_tests/charts/api_tests.py +++ b/tests/integration_tests/charts/api_tests.py @@ -291,7 +291,8 @@ def create_charts_some_with_tags(self, create_custom_tags): # noqa: F811 # rollback changes for association in tag_associations: - db.session.delete(association) + if db.session.query(TaggedObject).filter_by(id=association.id).first(): + db.session.delete(association) for chart in charts: db.session.delete(chart) db.session.commit() @@ -1970,8 +1971,7 @@ def test_gets_owned_created_favorited_by_me_filter(self): @parameterized.expand( [ - "Top 10 Girl Name Share", # Legacy chart - "Pivot Table v2", # Non-legacy chart + "Pivot Table v2", # Non-legacy charts ], ) @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") @@ -2116,7 +2116,7 @@ def test_warm_up_cache_no_datasource(self) -> None: "result": [ { "chart_id": slc.id, - "viz_error": "Chart's datasource does not exist", + "viz_error": "Chart's query context does not exist", "viz_status": None, }, ], diff --git a/tests/integration_tests/charts/commands_tests.py b/tests/integration_tests/charts/commands_tests.py index bcdd79b135791..3a193b40e21dc 100644 --- a/tests/integration_tests/charts/commands_tests.py +++ b/tests/integration_tests/charts/commands_tests.py @@ -389,6 +389,7 @@ def test_update_v1_response(self, mock_sm_g, mock_c_g, mock_u_g): @patch("superset.utils.core.g") @patch("superset.security.manager.g") @pytest.mark.usefixtures("load_energy_table_with_slice") + @pytest.mark.skip(reason="This test will be changed to use the api/v1/data") def test_query_context_update_command(self, mock_sm_g, mock_g): """ Test that a user can generate the chart query context @@ -421,6 +422,7 @@ def test_warm_up_cache_command_chart_not_found(self): ChartWarmUpCacheCommand(99999, None, None).run() @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") + @pytest.mark.skip(reason="This test will be changed to use the api/v1/data") def test_warm_up_cache(self): slc = self.get_slice("Top 10 Girl Name Share") result = ChartWarmUpCacheCommand(slc.id, None, None).run() diff --git a/tests/integration_tests/core_tests.py b/tests/integration_tests/core_tests.py index 38b899871fb68..4a47da95c46d3 100644 --- a/tests/integration_tests/core_tests.py +++ b/tests/integration_tests/core_tests.py @@ -42,9 +42,8 @@ from superset.db_engine_specs.base import BaseEngineSpec from superset.db_engine_specs.mssql import MssqlEngineSpec from superset.exceptions import SupersetException -from superset.extensions import async_query_manager_factory, cache_manager +from superset.extensions import cache_manager from superset.models import core as models -from superset.models.cache import CacheKey from superset.models.dashboard import Dashboard from superset.models.slice import Slice from superset.models.sql_lab import Query @@ -54,7 +53,6 @@ from superset.utils.core import backend from superset.utils.database import get_example_database from superset.views.database.views import DatabaseView -from tests.integration_tests.conftest import with_feature_flags from tests.integration_tests.constants import ADMIN_USERNAME, GAMMA_USERNAME from tests.integration_tests.fixtures.birth_names_dashboard import ( load_birth_names_dashboard_with_slices, # noqa: F401 @@ -136,10 +134,10 @@ def test_slice_endpoint(self): resp = self.client.get("/superset/slice/-1/") assert resp.status_code == 404 - @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") def test_viz_cache_key(self): self.login(ADMIN_USERNAME) - slc = self.get_slice("Top 10 Girl Name Share") + slc = self.get_slice("Life Expectancy VS Rural %") viz = slc.viz qobj = viz.query_obj() @@ -204,11 +202,15 @@ def test_save_slice(self): slc = db.session.query(Slice).filter_by(id=new_slice_id).one() assert slc.slice_name == copy_name + form_data["datasource"] = f"{tbl_id}__table" + form_data["slice_id"] = new_slice_id + + assert slc.form_data == form_data form_data.pop("slice_id") # We don't save the slice id when saving as - assert slc.viz.form_data == form_data form_data = { "adhoc_filters": [], + "datasource": f"{tbl_id}__table", "viz_type": "sankey", "groupby": ["source"], "metric": "sum__value", @@ -223,7 +225,7 @@ def test_save_slice(self): ) slc = db.session.query(Slice).filter_by(id=new_slice_id).one() assert slc.slice_name == new_slice_name - assert slc.viz.form_data == form_data + assert slc.form_data == form_data # Cleanup slices = ( @@ -345,34 +347,6 @@ def test_databaseview_edit(self): database.allow_run_async = False db.session.commit() - @pytest.mark.usefixtures( - "load_birth_names_dashboard_with_slices", - "load_energy_table_with_slice", - ) - def test_warm_up_cache(self): - self.login(ADMIN_USERNAME) - slc = self.get_slice("Top 10 Girl Name Share") - data = self.get_json_resp(f"/superset/warm_up_cache?slice_id={slc.id}") - assert data == [ - {"slice_id": slc.id, "viz_error": None, "viz_status": "success"} - ] - - data = self.get_json_resp( - "/superset/warm_up_cache?table_name=energy_usage&db_name=main" - ) - assert len(data) > 0 - - dashboard = self.get_dash_by_slug("births") - - assert self.get_json_resp( - f"/superset/warm_up_cache?dashboard_id={dashboard.id}&slice_id={slc.id}" - ) == [{"slice_id": slc.id, "viz_error": None, "viz_status": "success"}] - - assert self.get_json_resp( - f"/superset/warm_up_cache?dashboard_id={dashboard.id}&slice_id={slc.id}&extra_filters=" - + quote(json.dumps([{"col": "name", "op": "in", "val": ["Jennifer"]}])) - ) == [{"slice_id": slc.id, "viz_error": None, "viz_status": "success"}] - @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") def test_warm_up_cache_error(self) -> None: self.login(ADMIN_USERNAME) @@ -396,47 +370,6 @@ def test_warm_up_cache_error(self) -> None: } ] - @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") - def test_cache_logging(self): - self.login(ADMIN_USERNAME) - store_cache_keys = app.config["STORE_CACHE_KEYS_IN_METADATA_DB"] - app.config["STORE_CACHE_KEYS_IN_METADATA_DB"] = True - slc = self.get_slice("Top 10 Girl Name Share") - self.get_json_resp(f"/superset/warm_up_cache?slice_id={slc.id}") - ck = db.session.query(CacheKey).order_by(CacheKey.id.desc()).first() - assert ck.datasource_uid == f"{slc.table.id}__table" - db.session.delete(ck) - app.config["STORE_CACHE_KEYS_IN_METADATA_DB"] = store_cache_keys - - @with_feature_flags(KV_STORE=False) - def test_kv_disabled(self): - self.login(ADMIN_USERNAME) - - resp = self.client.get("/kv/10001/") - assert 404 == resp.status_code - - value = json.dumps({"data": "this is a test"}) - resp = self.client.post("/kv/store/", data=dict(data=value)) # noqa: C408 - assert resp.status_code == 404 - - @with_feature_flags(KV_STORE=True) - def test_kv_enabled(self): - self.login(ADMIN_USERNAME) - - resp = self.client.get("/kv/10001/") - assert 404 == resp.status_code - - value = json.dumps({"data": "this is a test"}) - resp = self.client.post("/kv/store/", data=dict(data=value)) # noqa: C408 - assert resp.status_code == 200 - kv = db.session.query(models.KeyValue).first() - kv_value = kv.value - assert json.loads(value) == json.loads(kv_value) - - resp = self.client.get(f"/kv/{kv.id}/") - assert resp.status_code == 200 - assert json.loads(value) == json.loads(resp.data.decode("utf-8")) - def test_gamma(self): self.login(GAMMA_USERNAME) assert "Charts" in self.get_resp("/chart/list/") @@ -558,263 +491,6 @@ def test_slice_payload_no_datasource(self): == "The dataset associated with this chart no longer exists" ) - @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") - def test_explore_json(self): - tbl_id = self.table_ids.get("birth_names") - form_data = { - "datasource": f"{tbl_id}__table", - "viz_type": "dist_bar", - "granularity_sqla": "ds", - "time_range": "No filter", - "metrics": ["count"], - "adhoc_filters": [], - "groupby": ["gender"], - "row_limit": 100, - } - self.login(ADMIN_USERNAME) - rv = self.client.post( - "/superset/explore_json/", - data={"form_data": json.dumps(form_data)}, - ) - data = json.loads(rv.data.decode("utf-8")) - - assert rv.status_code == 200 - assert data["rowcount"] == 2 - - @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") - def test_explore_json_dist_bar_order(self): - tbl_id = self.table_ids.get("birth_names") - form_data = { - "datasource": f"{tbl_id}__table", - "viz_type": "dist_bar", - "url_params": {}, - "granularity_sqla": "ds", - "time_range": 'DATEADD(DATETIME("2021-01-22T00:00:00"), -100, year) : 2021-01-22T00:00:00', # noqa: E501 - "metrics": [ - { - "expressionType": "SIMPLE", - "column": { - "id": 334, - "column_name": "name", - "verbose_name": "null", - "description": "null", - "expression": "", - "filterable": True, - "groupby": True, - "is_dttm": False, - "type": "VARCHAR(255)", - "python_date_format": "null", - }, - "aggregate": "COUNT", - "sqlExpression": "null", - "isNew": False, - "hasCustomLabel": False, - "label": "COUNT(name)", - "optionName": "metric_xdzsijn42f9_khi4h3v3vci", - }, - { - "expressionType": "SIMPLE", - "column": { - "id": 332, - "column_name": "ds", - "verbose_name": "null", - "description": "null", - "expression": "", - "filterable": True, - "groupby": True, - "is_dttm": True, - "type": "TIMESTAMP WITHOUT TIME ZONE", - "python_date_format": "null", - }, - "aggregate": "COUNT", - "sqlExpression": "null", - "isNew": False, - "hasCustomLabel": False, - "label": "COUNT(ds)", - "optionName": "metric_80g1qb9b6o7_ci5vquydcbe", - }, - ], - "order_desc": True, - "adhoc_filters": [], - "groupby": ["name"], - "columns": [], - "row_limit": 10, - "color_scheme": "supersetColors", - "label_colors": {}, - "show_legend": True, - "y_axis_format": "SMART_NUMBER", - "bottom_margin": "auto", - "x_ticks_layout": "auto", - } - - self.login(ADMIN_USERNAME) - rv = self.client.post( - "/superset/explore_json/", - data={"form_data": json.dumps(form_data)}, - ) - data = json.loads(rv.data.decode("utf-8")) - - resp = self.run_sql( - """ - SELECT count(name) AS count_name, count(ds) AS count_ds - FROM birth_names - WHERE ds >= '1921-01-22 00:00:00.000000' AND ds < '2021-01-22 00:00:00.000000' - GROUP BY name - ORDER BY count_name DESC - LIMIT 10; - """, # noqa: E501 - client_id="client_id_1", - username="admin", - ) - count_ds = [] - count_name = [] - for series in data["data"]: - if series["key"] == "COUNT(ds)": - count_ds = series["values"] - if series["key"] == "COUNT(name)": - count_name = series["values"] - for expected, actual_ds, actual_name in zip(resp["data"], count_ds, count_name): - assert expected["count_name"] == actual_name["y"] - assert expected["count_ds"] == actual_ds["y"] - - @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") - @mock.patch.dict( - "superset.extensions.feature_flag_manager._feature_flags", - GLOBAL_ASYNC_QUERIES=True, - ) - def test_explore_json_async(self): - tbl_id = self.table_ids.get("birth_names") - form_data = { - "datasource": f"{tbl_id}__table", - "viz_type": "dist_bar", - "granularity_sqla": "ds", - "time_range": "No filter", - "metrics": ["count"], - "adhoc_filters": [], - "groupby": ["gender"], - "row_limit": 100, - } - app._got_first_request = False - async_query_manager_factory.init_app(app) - self.login(ADMIN_USERNAME) - rv = self.client.post( - "/superset/explore_json/", - data={"form_data": json.dumps(form_data)}, - ) - data = json.loads(rv.data.decode("utf-8")) - keys = list(data.keys()) - - # If chart is cached, it will return 200, otherwise 202 - assert rv.status_code in {200, 202} - if rv.status_code == 202: - assert keys == [ - "channel_id", - "job_id", - "user_id", - "status", - "errors", - "result_url", - ] - - @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") - @mock.patch.dict( - "superset.extensions.feature_flag_manager._feature_flags", - GLOBAL_ASYNC_QUERIES=True, - ) - def test_explore_json_async_results_format(self): - tbl_id = self.table_ids.get("birth_names") - form_data = { - "datasource": f"{tbl_id}__table", - "viz_type": "dist_bar", - "granularity_sqla": "ds", - "time_range": "No filter", - "metrics": ["count"], - "adhoc_filters": [], - "groupby": ["gender"], - "row_limit": 100, - } - app._got_first_request = False - async_query_manager_factory.init_app(app) - self.login(ADMIN_USERNAME) - rv = self.client.post( - "/superset/explore_json/?results=true", - data={"form_data": json.dumps(form_data)}, - ) - assert rv.status_code == 200 - - @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") - @mock.patch( - "superset.utils.cache_manager.CacheManager.cache", - new_callable=mock.PropertyMock, - ) - @mock.patch("superset.viz.BaseViz.force_cached", new_callable=mock.PropertyMock) - def test_explore_json_data(self, mock_force_cached, mock_cache): - tbl_id = self.table_ids.get("birth_names") - form_data = dict( # noqa: C418 - { - "form_data": { - "datasource": f"{tbl_id}__table", - "viz_type": "dist_bar", - "granularity_sqla": "ds", - "time_range": "No filter", - "metrics": ["count"], - "adhoc_filters": [], - "groupby": ["gender"], - "row_limit": 100, - } - } - ) - - class MockCache: - def get(self, key): - return form_data - - def set(self): - return None - - mock_cache.return_value = MockCache() - mock_force_cached.return_value = False - - self.login(ADMIN_USERNAME) - rv = self.client.get("/superset/explore_json/data/valid-cache-key") - data = json.loads(rv.data.decode("utf-8")) - - assert rv.status_code == 200 - assert data["rowcount"] == 2 - - @mock.patch( - "superset.utils.cache_manager.CacheManager.cache", - new_callable=mock.PropertyMock, - ) - def test_explore_json_data_no_login(self, mock_cache): - tbl_id = self.table_ids.get("birth_names") - form_data = dict( # noqa: C418 - { - "form_data": { - "datasource": f"{tbl_id}__table", - "viz_type": "dist_bar", - "granularity_sqla": "ds", - "time_range": "No filter", - "metrics": ["count"], - "adhoc_filters": [], - "groupby": ["gender"], - "row_limit": 100, - } - } - ) - - class MockCache: - def get(self, key): - return form_data - - def set(self): - return None - - mock_cache.return_value = MockCache() - - rv = self.client.get("/superset/explore_json/data/valid-cache-key") - assert rv.status_code == 403 - def test_explore_json_data_invalid_cache_key(self): self.login(ADMIN_USERNAME) cache_key = "invalid-cache-key" diff --git a/tests/integration_tests/dashboards/api_tests.py b/tests/integration_tests/dashboards/api_tests.py index 2fde1c599dbfb..b5f2efa61c83a 100644 --- a/tests/integration_tests/dashboards/api_tests.py +++ b/tests/integration_tests/dashboards/api_tests.py @@ -41,6 +41,7 @@ from tests.integration_tests.base_api_tests import ApiOwnersTestCaseMixin from tests.integration_tests.base_tests import SupersetTestCase +from tests.integration_tests.conftest import with_feature_flags from tests.integration_tests.constants import ( ADMIN_USERNAME, ALPHA_USERNAME, @@ -3244,10 +3245,9 @@ def _get_screenshot(self, dashboard_id, cache_key, download_format): uri = f"/api/v1/dashboard/{dashboard_id}/screenshot/{cache_key}/?download_format={download_format}" # noqa: E501 return self.client.get(uri) + @with_feature_flags(THUMBNAILS=True, ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS=True) @pytest.mark.usefixtures("create_dashboard_with_tag") - @patch("superset.dashboards.api.is_feature_enabled") - def test_cache_dashboard_screenshot_success(self, is_feature_enabled): - is_feature_enabled.return_value = True + def test_cache_dashboard_screenshot_success(self): self.login(ADMIN_USERNAME) dashboard = ( db.session.query(Dashboard) @@ -3257,10 +3257,9 @@ def test_cache_dashboard_screenshot_success(self, is_feature_enabled): response = self._cache_screenshot(dashboard.id) assert response.status_code == 202 + @with_feature_flags(THUMBNAILS=True, ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS=True) @pytest.mark.usefixtures("create_dashboard_with_tag") - @patch("superset.dashboards.api.is_feature_enabled") - def test_cache_dashboard_screenshot_dashboard_validation(self, is_feature_enabled): - is_feature_enabled.return_value = True + def test_cache_dashboard_screenshot_dashboard_validation(self): self.login(ADMIN_USERNAME) dashboard = ( db.session.query(Dashboard) @@ -3276,25 +3275,21 @@ def test_cache_dashboard_screenshot_dashboard_validation(self, is_feature_enable response = self._cache_screenshot(dashboard.id, invalid_payload) assert response.status_code == 400 - @patch("superset.dashboards.api.is_feature_enabled") - def test_cache_dashboard_screenshot_dashboard_not_found(self, is_feature_enabled): - is_feature_enabled.return_value = True + @with_feature_flags(THUMBNAILS=True, ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS=True) + def test_cache_dashboard_screenshot_dashboard_not_found(self): self.login(ADMIN_USERNAME) non_existent_id = 999 response = self._cache_screenshot(non_existent_id) assert response.status_code == 404 + @with_feature_flags(THUMBNAILS=True, ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS=True) @pytest.mark.usefixtures("create_dashboard_with_tag") @patch("superset.dashboards.api.cache_dashboard_screenshot") @patch("superset.dashboards.api.DashboardScreenshot.get_from_cache_key") - @patch("superset.dashboards.api.is_feature_enabled") - def test_screenshot_success_png( - self, is_feature_enabled, mock_get_cache, mock_cache_task - ): + def test_screenshot_success_png(self, mock_get_cache, mock_cache_task): """ Validate screenshot returns png """ - is_feature_enabled.return_value = True self.login(ADMIN_USERNAME) mock_cache_task.return_value = None mock_get_cache.return_value = BytesIO(b"fake image data") @@ -3313,18 +3308,17 @@ def test_screenshot_success_png( assert response.mimetype == "image/png" assert response.data == b"fake image data" + @with_feature_flags(THUMBNAILS=True, ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS=True) @pytest.mark.usefixtures("create_dashboard_with_tag") @patch("superset.dashboards.api.cache_dashboard_screenshot") @patch("superset.dashboards.api.build_pdf_from_screenshots") @patch("superset.dashboards.api.DashboardScreenshot.get_from_cache_key") - @patch("superset.dashboards.api.is_feature_enabled") def test_screenshot_success_pdf( - self, is_feature_enabled, mock_get_from_cache, mock_build_pdf, mock_cache_task + self, mock_get_from_cache, mock_build_pdf, mock_cache_task ): """ Validate screenshot can return pdf. """ - is_feature_enabled.return_value = True self.login(ADMIN_USERNAME) mock_cache_task.return_value = None mock_get_from_cache.return_value = BytesIO(b"fake image data") @@ -3344,14 +3338,11 @@ def test_screenshot_success_pdf( assert response.mimetype == "application/pdf" assert response.data == b"fake pdf data" + @with_feature_flags(THUMBNAILS=True, ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS=True) @pytest.mark.usefixtures("create_dashboard_with_tag") @patch("superset.dashboards.api.cache_dashboard_screenshot") @patch("superset.dashboards.api.DashboardScreenshot.get_from_cache_key") - @patch("superset.dashboards.api.is_feature_enabled") - def test_screenshot_not_in_cache( - self, is_feature_enabled, mock_get_cache, mock_cache_task - ): - is_feature_enabled.return_value = True + def test_screenshot_not_in_cache(self, mock_get_cache, mock_cache_task): self.login(ADMIN_USERNAME) mock_cache_task.return_value = None mock_get_cache.return_value = None @@ -3368,22 +3359,18 @@ def test_screenshot_not_in_cache( response = self._get_screenshot(dashboard.id, cache_key, "pdf") assert response.status_code == 404 - @patch("superset.dashboards.api.is_feature_enabled") - def test_screenshot_dashboard_not_found(self, is_feature_enabled): - is_feature_enabled.return_value = True + @with_feature_flags(THUMBNAILS=True, ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS=True) + def test_screenshot_dashboard_not_found(self): self.login(ADMIN_USERNAME) non_existent_id = 999 response = self._get_screenshot(non_existent_id, "some_cache_key", "png") assert response.status_code == 404 + @with_feature_flags(THUMBNAILS=True, ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS=True) @pytest.mark.usefixtures("create_dashboard_with_tag") @patch("superset.dashboards.api.cache_dashboard_screenshot") @patch("superset.dashboards.api.DashboardScreenshot.get_from_cache_key") - @patch("superset.dashboards.api.is_feature_enabled") - def test_screenshot_invalid_download_format( - self, is_feature_enabled, mock_get_cache, mock_cache_task - ): - is_feature_enabled.return_value = True + def test_screenshot_invalid_download_format(self, mock_get_cache, mock_cache_task): self.login(ADMIN_USERNAME) mock_cache_task.return_value = None mock_get_cache.return_value = BytesIO(b"fake png data") @@ -3401,10 +3388,41 @@ def test_screenshot_invalid_download_format( response = self._get_screenshot(dashboard.id, cache_key, "invalid") assert response.status_code == 404 + @with_feature_flags(THUMBNAILS=False, ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS=True) + @pytest.mark.usefixtures("create_dashboard_with_tag") + def test_cache_dashboard_screenshot_feature_thumbnails_ff_disabled(self): + self.login(ADMIN_USERNAME) + + dashboard = ( + db.session.query(Dashboard) + .filter(Dashboard.dashboard_title == "dash with tag") + .first() + ) + + assert dashboard is not None + + response = self._cache_screenshot(dashboard.id) + assert response.status_code == 404 + + @with_feature_flags(THUMBNAILS=True, ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS=False) + @pytest.mark.usefixtures("create_dashboard_with_tag") + def test_cache_dashboard_screenshot_feature_screenshot_ff_disabled(self): + self.login(ADMIN_USERNAME) + + dashboard = ( + db.session.query(Dashboard) + .filter(Dashboard.dashboard_title == "dash with tag") + .first() + ) + + assert dashboard is not None + + response = self._cache_screenshot(dashboard.id) + assert response.status_code == 404 + + @with_feature_flags(THUMBNAILS=False, ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS=False) @pytest.mark.usefixtures("create_dashboard_with_tag") - @patch("superset.dashboards.api.is_feature_enabled") - def test_cache_dashboard_screenshot_feature_disabled(self, is_feature_enabled): - is_feature_enabled.return_value = False + def test_cache_dashboard_screenshot_feature_both_ff_disabled(self): self.login(ADMIN_USERNAME) dashboard = ( diff --git a/tests/integration_tests/db_engine_specs/presto_tests.py b/tests/integration_tests/db_engine_specs/presto_tests.py index 6ee639552ccf9..c57bec88008d0 100644 --- a/tests/integration_tests/db_engine_specs/presto_tests.py +++ b/tests/integration_tests/db_engine_specs/presto_tests.py @@ -87,7 +87,7 @@ def verify_presto_column(self, column, expected_results): inspector.bind.execute.return_value.fetchall = mock.Mock(return_value=[row]) results = PrestoEngineSpec.get_columns(inspector, Table("", "")) assert len(expected_results) == len(results) - for expected_result, result in zip(expected_results, results): + for expected_result, result in zip(expected_results, results, strict=False): assert expected_result[0] == result["column_name"] assert expected_result[1] == str(result["type"]) @@ -191,7 +191,9 @@ def test_presto_get_fields(self): "label": 'column."quoted.nested obj"', }, ] - for actual_result, expected_result in zip(actual_results, expected_results): + for actual_result, expected_result in zip( + actual_results, expected_results, strict=False + ): assert actual_result.element.name == expected_result["column_name"] assert actual_result.name == expected_result["label"] diff --git a/tests/integration_tests/dict_import_export_tests.py b/tests/integration_tests/dict_import_export_tests.py index bff144630f4cf..c9f1436f06680 100644 --- a/tests/integration_tests/dict_import_export_tests.py +++ b/tests/integration_tests/dict_import_export_tests.py @@ -80,7 +80,8 @@ def create_table( "id": id, "params": json.dumps(params), "columns": [ - {"column_name": c, "uuid": u} for c, u in zip(cols_names, cols_uuids) + {"column_name": c, "uuid": u} + for c, u in zip(cols_names, cols_uuids, strict=False) ], "metrics": [{"metric_name": c, "expression": ""} for c in metric_names], } @@ -88,7 +89,7 @@ def create_table( table = SqlaTable( id=id, schema=schema, table_name=name, params=json.dumps(params) ) - for col_name, uuid in zip(cols_names, cols_uuids): + for col_name, uuid in zip(cols_names, cols_uuids, strict=False): table.columns.append(TableColumn(column_name=col_name, uuid=uuid)) for metric_name in metric_names: table.metrics.append(SqlMetric(metric_name=metric_name, expression="")) diff --git a/tests/integration_tests/import_export_tests.py b/tests/integration_tests/import_export_tests.py index d4acc010347f3..89b29570884d3 100644 --- a/tests/integration_tests/import_export_tests.py +++ b/tests/integration_tests/import_export_tests.py @@ -153,7 +153,7 @@ def assert_dash_equals( assert len(expected_dash.slices) == len(actual_dash.slices) expected_slices = sorted(expected_dash.slices, key=lambda s: s.slice_name or "") actual_slices = sorted(actual_dash.slices, key=lambda s: s.slice_name or "") - for e_slc, a_slc in zip(expected_slices, actual_slices): + for e_slc, a_slc in zip(expected_slices, actual_slices, strict=False): self.assert_slice_equals(e_slc, a_slc) if check_position: assert expected_dash.position_json == actual_dash.position_json @@ -212,7 +212,7 @@ def assert_only_exported_slc_fields(self, expected_dash, actual_dash): """ expected_slices = sorted(expected_dash.slices, key=lambda s: s.slice_name or "") actual_slices = sorted(actual_dash.slices, key=lambda s: s.slice_name or "") - for e_slc, a_slc in zip(expected_slices, actual_slices): + for e_slc, a_slc in zip(expected_slices, actual_slices, strict=False): params = a_slc.params_dict assert e_slc.datasource.name == params["datasource_name"] assert e_slc.datasource.schema == params["schema"] diff --git a/tests/integration_tests/reports/alert_tests.py b/tests/integration_tests/reports/alert_tests.py index b4dfdabd1a4ca..16ce8f3fed34b 100644 --- a/tests/integration_tests/reports/alert_tests.py +++ b/tests/integration_tests/reports/alert_tests.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. # pylint: disable=invalid-name, unused-argument, import-outside-toplevel +import uuid from contextlib import nullcontext, suppress from typing import Optional, Union @@ -84,7 +85,7 @@ def test_execute_query_as_report_executor( database=get_example_database(), validator_config_json='{"op": "==", "threshold": 1}', ) - command = AlertCommand(report_schedule=report_schedule) + command = AlertCommand(report_schedule=report_schedule, execution_id=uuid.uuid4()) override_user_mock = mocker.patch("superset.commands.report.alert.override_user") cm = ( pytest.raises(type(expected_result)) @@ -98,6 +99,83 @@ def test_execute_query_as_report_executor( app.config["ALERT_REPORTS_EXECUTE_AS"] = original_config +def test_execute_query_mutate_query_enabled( + mocker: MockerFixture, + app_context: AppContext, + get_user, +) -> None: + from superset.commands.report.alert import AlertCommand + from superset.reports.models import ReportSchedule + + default_alert_mutate_ff = app.config["MUTATE_ALERT_QUERY"] + + app.config["MUTATE_ALERT_QUERY"] = True + mocker.patch("superset.commands.report.alert.override_user") + mock_df = mocker.MagicMock(spec=pd.DataFrame) + mock_df.empty = True + mock_database = get_example_database() + mock_get_df = mocker.patch.object(mock_database, "get_df", return_value=mock_df) + mock_limited_sql = mocker.patch.object(mock_database, "apply_limit_to_sql") + mock_mutate_call = mocker.patch.object(mock_database, "mutate_sql_based_on_config") + + report_schedule = ReportSchedule( + created_by=get_user("admin"), + owners=[get_user("admin")], + type=ReportScheduleType.ALERT, + description="description", + crontab="0 9 * * *", + creation_method=ReportCreationMethod.ALERTS_REPORTS, + sql="SELECT 1", + grace_period=14400, + working_timeout=3600, + database=mock_database, + validator_config_json='{"op": "==", "threshold": 1}', + ) + AlertCommand(report_schedule=report_schedule, execution_id=uuid.uuid4()).run() + + mock_mutate_call.assert_called_once_with(mock_limited_sql.return_value) + mock_get_df.assert_called_once_with(sql=mock_mutate_call.return_value) + + app.config["MUTATE_ALERT_QUERY"] = default_alert_mutate_ff + + +def test_execute_query_mutate_query_disabled( + mocker: MockerFixture, + app_context: AppContext, + get_user, +) -> None: + from superset.commands.report.alert import AlertCommand + from superset.reports.models import ReportSchedule + + default_alert_mutate_ff = app.config["MUTATE_ALERT_QUERY"] + + app.config["MUTATE_ALERT_QUERY"] = False + mocker.patch("superset.commands.report.alert.override_user") + mock_database = mocker.MagicMock() + + report_schedule = ReportSchedule( + created_by=get_user("admin"), + owners=[get_user("admin")], + type=ReportScheduleType.ALERT, + description="description", + crontab="0 9 * * *", + creation_method=ReportCreationMethod.ALERTS_REPORTS, + sql="SELECT 1", + grace_period=14400, + working_timeout=3600, + database=mock_database, + validator_config_json='{"op": "==", "threshold": 1}', + ) + AlertCommand(report_schedule=report_schedule, execution_id=uuid.uuid4()).run() + + mock_database.mutate_sql_based_on_config.assert_not_called() + mock_database.get_df.assert_called_once_with( + sql=mock_database.apply_limit_to_sql.return_value + ) + + app.config["MUTATE_ALERT_QUERY"] = default_alert_mutate_ff + + def test_execute_query_succeeded_no_retry( mocker: MockerFixture, app_context: None ) -> None: @@ -108,7 +186,7 @@ def test_execute_query_succeeded_no_retry( side_effect=lambda: pd.DataFrame([{"sample_col": 0}]), ) - command = AlertCommand(report_schedule=mocker.Mock()) + command = AlertCommand(report_schedule=mocker.Mock(), execution_id=uuid.uuid4()) command.validate() @@ -140,7 +218,7 @@ def _mocked_execute_query() -> pd.DataFrame: execute_query_mock.side_effect = _mocked_execute_query execute_query_mock.__name__ = "mocked_execute_query" - command = AlertCommand(report_schedule=mocker.Mock()) + command = AlertCommand(report_schedule=mocker.Mock(), execution_id=uuid.uuid4()) command.validate() @@ -162,7 +240,7 @@ def _mocked_execute_query() -> None: execute_query_mock.side_effect = _mocked_execute_query execute_query_mock.__name__ = "mocked_execute_query" - command = AlertCommand(report_schedule=mocker.Mock()) + command = AlertCommand(report_schedule=mocker.Mock(), execution_id=uuid.uuid4()) with suppress(AlertQueryTimeout): command.validate() @@ -184,9 +262,42 @@ def _mocked_execute_query() -> None: execute_query_mock.side_effect = _mocked_execute_query execute_query_mock.__name__ = "mocked_execute_query" - command = AlertCommand(report_schedule=mocker.Mock()) + command = AlertCommand(report_schedule=mocker.Mock(), execution_id=uuid.uuid4()) with suppress(AlertQueryError): command.validate() # Should match the value defined in superset_test_config.py assert execute_query_mock.call_count == 3 + + +def test_get_alert_metadata_from_object( + mocker: MockerFixture, + app_context: AppContext, + get_user, +) -> None: + from superset.commands.report.alert import AlertCommand + from superset.reports.models import ReportSchedule + + app.config["ALERT_REPORTS_EXECUTE_AS"] = [ExecutorType.OWNER] + + mock_database = mocker.MagicMock() + mock_exec_id = uuid.uuid4() + report_schedule = ReportSchedule( + created_by=get_user("admin"), + owners=[get_user("admin")], + type=ReportScheduleType.ALERT, + description="description", + crontab="0 9 * * *", + creation_method=ReportCreationMethod.ALERTS_REPORTS, + sql="SELECT 1", + grace_period=14400, + working_timeout=3600, + database=mock_database, + validator_config_json='{"op": "==", "threshold": 1}', + ) + + cm = AlertCommand(report_schedule=report_schedule, execution_id=mock_exec_id) + assert cm._get_alert_metadata_from_object() == { + "report_schedule_id": report_schedule.id, + "execution_id": mock_exec_id, + } diff --git a/tests/integration_tests/sql_lab/permalink/__init__.py b/tests/integration_tests/sql_lab/permalink/__init__.py new file mode 100644 index 0000000000000..13a83393a9124 --- /dev/null +++ b/tests/integration_tests/sql_lab/permalink/__init__.py @@ -0,0 +1,16 @@ +# 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. diff --git a/tests/integration_tests/sql_lab/permalink/api_tests.py b/tests/integration_tests/sql_lab/permalink/api_tests.py new file mode 100644 index 0000000000000..7d515b796f8a7 --- /dev/null +++ b/tests/integration_tests/sql_lab/permalink/api_tests.py @@ -0,0 +1,102 @@ +# 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 json +from collections.abc import Iterator +from typing import Any +from uuid import uuid3 + +import pytest + +from superset import db +from superset.key_value.models import KeyValueEntry +from superset.key_value.types import KeyValueResource +from superset.key_value.utils import decode_permalink_id +from tests.integration_tests.constants import ( + GAMMA_SQLLAB_USERNAME, +) + + +@pytest.fixture +def tab_state_data() -> dict[str, Any]: + return { + "dbId": 1, + "name": "Untitled Query 1", + "schema": "main", + "sql": "SELECT 'foo' as bar", + "autorun": False, + "templateParams": '{"param1": "value1"}', + } + + +@pytest.fixture +def permalink_salt(app_context) -> Iterator[str]: + from superset.key_value.shared_entries import get_permalink_salt, get_uuid_namespace + from superset.key_value.types import SharedKey + + key = SharedKey.SQLLAB_PERMALINK_SALT + salt = get_permalink_salt(key) + yield salt + namespace = get_uuid_namespace(salt) + db.session.query(KeyValueEntry).filter_by( + resource=KeyValueResource.APP, + uuid=uuid3(namespace, key), + ) + db.session.commit() + + +def test_post( + tab_state_data: dict[str, Any], permalink_salt: str, test_client, login_as +): + login_as(GAMMA_SQLLAB_USERNAME) + resp = test_client.post("api/v1/sqllab/permalink", json=tab_state_data) + assert resp.status_code == 201 + data = resp.json + key = data["key"] + url = data["url"] + assert key in url + id_ = decode_permalink_id(key, permalink_salt) + db.session.query(KeyValueEntry).filter_by(id=id_).delete() + db.session.commit() + + +def test_post_access_denied(tab_state_data: dict[str, Any], test_client, login_as): + resp = test_client.post("api/v1/sqllab/permalink", json=tab_state_data) + assert resp.status_code == 401 + + +def test_post_invalid_schema(test_client, login_as): + login_as(GAMMA_SQLLAB_USERNAME) + resp = test_client.post( + "api/v1/sqllab/permalink", json={"name": "Untitled Query 1", "sql": "Test"} + ) + assert resp.status_code == 400 + + +def test_get( + tab_state_data: dict[str, Any], permalink_salt: str, test_client, login_as +): + login_as(GAMMA_SQLLAB_USERNAME) + resp = test_client.post("api/v1/sqllab/permalink", json=tab_state_data) + data = resp.json + key = data["key"] + resp = test_client.get(f"api/v1/sqllab/permalink/{key}") + assert resp.status_code == 200 + result = json.loads(resp.data.decode("utf-8")) + assert result == tab_state_data + id_ = decode_permalink_id(key, permalink_salt) + db.session.query(KeyValueEntry).filter_by(id=id_).delete() + db.session.commit() diff --git a/tests/integration_tests/superset_test_config.py b/tests/integration_tests/superset_test_config.py index 4358ba875bc97..a59f6e9a4f494 100644 --- a/tests/integration_tests/superset_test_config.py +++ b/tests/integration_tests/superset_test_config.py @@ -68,8 +68,6 @@ FEATURE_FLAGS = { **FEATURE_FLAGS, # noqa: F405 "foo": "bar", - "KV_STORE": True, - "SHARE_QUERIES_VIA_KV_STORE": True, "ENABLE_TEMPLATE_PROCESSING": True, "ALERT_REPORTS": True, "AVOID_COLORS_COLLISION": True, @@ -151,3 +149,4 @@ class CeleryConfig: } PRESERVE_CONTEXT_ON_EXCEPTION = False +print("Loaded TEST config for INTEGRATION tests") diff --git a/tests/integration_tests/superset_test_config_thumbnails.py b/tests/integration_tests/superset_test_config_thumbnails.py index 010e587718e97..594b0b509b1b9 100644 --- a/tests/integration_tests/superset_test_config_thumbnails.py +++ b/tests/integration_tests/superset_test_config_thumbnails.py @@ -74,8 +74,6 @@ class CeleryConfig: FEATURE_FLAGS = { "foo": "bar", - "KV_STORE": False, - "SHARE_QUERIES_VIA_KV_STORE": False, "THUMBNAILS": True, "THUMBNAILS_SQLA_LISTENERS": False, } diff --git a/tests/integration_tests/tags/api_tests.py b/tests/integration_tests/tags/api_tests.py index c9a718f60d935..79022d2805eb0 100644 --- a/tests/integration_tests/tags/api_tests.py +++ b/tests/integration_tests/tags/api_tests.py @@ -487,8 +487,19 @@ def test_add_tag_not_found(self): @pytest.mark.usefixtures("create_tags") def test_delete_favorite_tag_not_found(self): + """ + Tag API: Test trying to remove an unexisting tag from the list + of user favorites returns 404. + """ self.login(ADMIN_USERNAME) - uri = "api/v1/tag/123/favorites/" # noqa: F541 + + # Fetch all existing tag IDs + existing_ids = [tag_id for (tag_id,) in db.session.query(Tag.id).all()] + + # Get an ID not in use + non_existent_id = max(existing_ids, default=0) + 1 + + uri = f"api/v1/tag/{non_existent_id}/favorites/" # noqa: F541 rv = self.client.delete(uri, follow_redirects=True) assert rv.status_code == 404 diff --git a/tests/integration_tests/tasks/async_queries_tests.py b/tests/integration_tests/tasks/async_queries_tests.py index 4462fa537ba28..728076011bd43 100644 --- a/tests/integration_tests/tasks/async_queries_tests.py +++ b/tests/integration_tests/tasks/async_queries_tests.py @@ -20,7 +20,6 @@ from uuid import uuid4 import pytest -import redis from celery.exceptions import SoftTimeLimitExceeded from parameterized import parameterized @@ -52,7 +51,6 @@ class TestAsyncQueries(SupersetTestCase): [ ("RedisCacheBackend", mock.Mock(spec=RedisCacheBackend)), ("RedisSentinelCacheBackend", mock.Mock(spec=RedisSentinelCacheBackend)), - ("redis.Redis", mock.Mock(spec=redis.Redis)), ] ) @mock.patch("superset.tasks.async_queries.set_form_data") @@ -88,7 +86,6 @@ def test_load_chart_data_into_cache( [ ("RedisCacheBackend", mock.Mock(spec=RedisCacheBackend)), ("RedisSentinelCacheBackend", mock.Mock(spec=RedisSentinelCacheBackend)), - ("redis.Redis", mock.Mock(spec=redis.Redis)), ] ) @mock.patch.object( @@ -125,7 +122,6 @@ def test_load_chart_data_into_cache_error( [ ("RedisCacheBackend", mock.Mock(spec=RedisCacheBackend)), ("RedisSentinelCacheBackend", mock.Mock(spec=RedisSentinelCacheBackend)), - ("redis.Redis", mock.Mock(spec=redis.Redis)), ] ) @mock.patch.object(ChartDataCommand, "run") @@ -163,10 +159,10 @@ def test_soft_timeout_load_chart_data_into_cache( [ ("RedisCacheBackend", mock.Mock(spec=RedisCacheBackend)), ("RedisSentinelCacheBackend", mock.Mock(spec=RedisSentinelCacheBackend)), - ("redis.Redis", mock.Mock(spec=redis.Redis)), ] ) @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") + @pytest.mark.skip(reason="This test will be changed to use the api/v1/data") @mock.patch.object(async_query_manager, "update_job") def test_load_explore_json_into_cache( self, cache_type, cache_backend, mock_update_job @@ -208,7 +204,6 @@ def test_load_explore_json_into_cache( [ ("RedisCacheBackend", mock.Mock(spec=RedisCacheBackend)), ("RedisSentinelCacheBackend", mock.Mock(spec=RedisSentinelCacheBackend)), - ("redis.Redis", mock.Mock(spec=redis.Redis)), ] ) @mock.patch.object(async_query_manager, "update_job") @@ -244,7 +239,6 @@ def test_load_explore_json_into_cache_error( [ ("RedisCacheBackend", mock.Mock(spec=RedisCacheBackend)), ("RedisSentinelCacheBackend", mock.Mock(spec=RedisSentinelCacheBackend)), - ("redis.Redis", mock.Mock(spec=redis.Redis)), ] ) @mock.patch.object(ChartDataCommand, "run") diff --git a/tests/integration_tests/utils_tests.py b/tests/integration_tests/utils_tests.py index 70bebde9e3c3d..ca49d65fc09db 100644 --- a/tests/integration_tests/utils_tests.py +++ b/tests/integration_tests/utils_tests.py @@ -879,11 +879,11 @@ def test_get_form_data_corrupted_json(self) -> None: assert form_data == {} assert slc is None - @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") def test_log_this(self) -> None: # TODO: Add additional scenarios. self.login(ADMIN_USERNAME) - slc = self.get_slice("Top 10 Girl Name Share") + slc = self.get_slice("Life Expectancy VS Rural %") dashboard_id = 1 assert slc.viz is not None diff --git a/tests/integration_tests/viz_tests.py b/tests/integration_tests/viz_tests.py index 5777cbd0f00e3..dde6a1dec8ffa 100644 --- a/tests/integration_tests/viz_tests.py +++ b/tests/integration_tests/viz_tests.py @@ -17,7 +17,6 @@ # isort:skip_file from datetime import datetime import logging -from math import nan from unittest.mock import Mock, patch import numpy as np @@ -27,7 +26,6 @@ import tests.integration_tests.test_app # noqa: F401 import superset.viz as viz from superset import app -from superset.constants import NULL_STRING from superset.exceptions import QueryObjectValidationError, SpatialException from superset.utils.core import DTTM_ALIAS @@ -177,174 +175,6 @@ def test_cache_timeout(self): app.config["DATA_CACHE_CONFIG"]["CACHE_DEFAULT_TIMEOUT"] = data_cache_timeout -class TestDistBarViz(SupersetTestCase): - def test_groupby_nulls(self): - form_data = { - "metrics": ["votes"], - "adhoc_filters": [], - "groupby": ["toppings"], - "columns": [], - "order_desc": True, - } - datasource = self.get_datasource_mock() - df = pd.DataFrame( - { - "toppings": ["cheese", "pepperoni", "anchovies", None], - "votes": [3, 5, 1, 2], - } - ) - test_viz = viz.DistributionBarViz(datasource, form_data) - data = test_viz.get_data(df)[0] - assert "votes" == data["key"] - expected_values = [ - {"x": "pepperoni", "y": 5}, - {"x": "cheese", "y": 3}, - {"x": NULL_STRING, "y": 2}, - {"x": "anchovies", "y": 1}, - ] - assert expected_values == data["values"] - - def test_groupby_nans(self): - form_data = { - "metrics": ["count"], - "adhoc_filters": [], - "groupby": ["beds"], - "columns": [], - "order_desc": True, - } - datasource = self.get_datasource_mock() - df = pd.DataFrame({"beds": [0, 1, nan, 2], "count": [30, 42, 3, 29]}) - test_viz = viz.DistributionBarViz(datasource, form_data) - data = test_viz.get_data(df)[0] - assert "count" == data["key"] - expected_values = [ - {"x": "1.0", "y": 42}, - {"x": "0.0", "y": 30}, - {"x": "2.0", "y": 29}, - {"x": NULL_STRING, "y": 3}, - ] - - assert expected_values == data["values"] - - def test_column_nulls(self): - form_data = { - "metrics": ["votes"], - "adhoc_filters": [], - "groupby": ["toppings"], - "columns": ["role"], - "order_desc": True, - } - datasource = self.get_datasource_mock() - df = pd.DataFrame( - { - "toppings": ["cheese", "pepperoni", "cheese", "pepperoni"], - "role": ["engineer", "engineer", None, None], - "votes": [3, 5, 1, 2], - } - ) - test_viz = viz.DistributionBarViz(datasource, form_data) - data = test_viz.get_data(df) - expected = [ - { - "key": NULL_STRING, - "values": [{"x": "pepperoni", "y": 2}, {"x": "cheese", "y": 1}], - }, - { - "key": "engineer", - "values": [{"x": "pepperoni", "y": 5}, {"x": "cheese", "y": 3}], - }, - ] - assert expected == data - - def test_column_metrics_in_order(self): - form_data = { - "metrics": ["z_column", "votes", "a_column"], - "adhoc_filters": [], - "groupby": ["toppings"], - "columns": [], - "order_desc": True, - } - datasource = self.get_datasource_mock() - df = pd.DataFrame( - { - "toppings": ["cheese", "pepperoni", "cheese", "pepperoni"], - "role": ["engineer", "engineer", None, None], - "votes": [3, 5, 1, 2], - "a_column": [3, 5, 1, 2], - "z_column": [3, 5, 1, 2], - } - ) - test_viz = viz.DistributionBarViz(datasource, form_data) - data = test_viz.get_data(df) - - expected = [ - { - "key": "z_column", - "values": [{"x": "pepperoni", "y": 3.5}, {"x": "cheese", "y": 2.0}], - }, - { - "key": "votes", - "values": [{"x": "pepperoni", "y": 3.5}, {"x": "cheese", "y": 2.0}], - }, - { - "key": "a_column", - "values": [{"x": "pepperoni", "y": 3.5}, {"x": "cheese", "y": 2.0}], - }, - ] - - assert expected == data - - def test_column_metrics_in_order_with_breakdowns(self): - form_data = { - "metrics": ["z_column", "votes", "a_column"], - "adhoc_filters": [], - "groupby": ["toppings"], - "columns": ["role"], - "order_desc": True, - } - datasource = self.get_datasource_mock() - df = pd.DataFrame( - { - "toppings": ["cheese", "pepperoni", "cheese", "pepperoni"], - "role": ["engineer", "engineer", None, None], - "votes": [3, 5, 1, 2], - "a_column": [3, 5, 1, 2], - "z_column": [3, 5, 1, 2], - } - ) - test_viz = viz.DistributionBarViz(datasource, form_data) - data = test_viz.get_data(df) - - expected = [ - { - "key": f"z_column, {NULL_STRING}", - "values": [{"x": "pepperoni", "y": 2}, {"x": "cheese", "y": 1}], - }, - { - "key": "z_column, engineer", - "values": [{"x": "pepperoni", "y": 5}, {"x": "cheese", "y": 3}], - }, - { - "key": f"votes, {NULL_STRING}", - "values": [{"x": "pepperoni", "y": 2}, {"x": "cheese", "y": 1}], - }, - { - "key": "votes, engineer", - "values": [{"x": "pepperoni", "y": 5}, {"x": "cheese", "y": 3}], - }, - { - "key": f"a_column, {NULL_STRING}", - "values": [{"x": "pepperoni", "y": 2}, {"x": "cheese", "y": 1}], - }, - { - "key": "a_column, engineer", - "values": [{"x": "pepperoni", "y": 5}, {"x": "cheese", "y": 3}], - }, - ] - - assert expected == data - - class TestPairedTTest(SupersetTestCase): def test_get_data_transforms_dataframe(self): form_data = { diff --git a/tests/unit_tests/async_events/async_query_manager_tests.py b/tests/unit_tests/async_events/async_query_manager_tests.py index 005058b00f97c..15e3ce68fce61 100644 --- a/tests/unit_tests/async_events/async_query_manager_tests.py +++ b/tests/unit_tests/async_events/async_query_manager_tests.py @@ -17,7 +17,6 @@ from unittest import mock from unittest.mock import ANY, Mock -import redis from flask import g from jwt import encode from pytest import fixture, mark, raises # noqa: PT013 @@ -84,7 +83,6 @@ def test_parse_channel_id_from_request_bad_jwt(async_query_manager): [ ("RedisCacheBackend", mock.Mock(spec=RedisCacheBackend)), ("RedisSentinelCacheBackend", mock.Mock(spec=RedisSentinelCacheBackend)), - ("redis.Redis", mock.Mock(spec=redis.Redis)), ], ) @mock.patch("superset.is_feature_enabled") @@ -129,7 +127,6 @@ def test_submit_chart_data_job_as_guest_user( [ ("RedisCacheBackend", mock.Mock(spec=RedisCacheBackend)), ("RedisSentinelCacheBackend", mock.Mock(spec=RedisSentinelCacheBackend)), - ("redis.Redis", mock.Mock(spec=redis.Redis)), ], ) @mock.patch("superset.is_feature_enabled") diff --git a/tests/unit_tests/commands/databases/update_test.py b/tests/unit_tests/commands/databases/update_test.py index 7ca3d70dc4c7c..daf41b7506888 100644 --- a/tests/unit_tests/commands/databases/update_test.py +++ b/tests/unit_tests/commands/databases/update_test.py @@ -21,8 +21,10 @@ from pytest_mock import MockerFixture from superset.commands.database.update import UpdateDatabaseCommand +from superset.db_engine_specs.base import BaseEngineSpec from superset.exceptions import OAuth2RedirectError from superset.extensions import security_manager +from superset.utils import json oauth2_client_info = { "id": "client_id", @@ -82,7 +84,10 @@ def database_needs_oauth2(mocker: MockerFixture) -> MagicMock: "tab_id", "redirect_uri", ) - database.get_oauth2_config.return_value = oauth2_client_info + database.encrypted_extra = json.dumps({"oauth2_client_info": oauth2_client_info}) + database.db_engine_spec.unmask_encrypted_extra = ( + BaseEngineSpec.unmask_encrypted_extra + ) return database @@ -332,10 +337,6 @@ def test_update_with_oauth2( "add_permission_view_menu", ) - database_needs_oauth2.db_engine_spec.unmask_encrypted_extra.return_value = { - "oauth2_client_info": oauth2_client_info, - } - UpdateDatabaseCommand(1, {}).run() add_permission_view_menu.assert_not_called() @@ -368,11 +369,129 @@ def test_update_with_oauth2_changed( modified_oauth2_client_info = oauth2_client_info.copy() modified_oauth2_client_info["scope"] = "scope-b" - database_needs_oauth2.db_engine_spec.unmask_encrypted_extra.return_value = { - "oauth2_client_info": modified_oauth2_client_info, - } - UpdateDatabaseCommand(1, {}).run() + UpdateDatabaseCommand( + 1, + { + "masked_encrypted_extra": json.dumps( + {"oauth2_client_info": modified_oauth2_client_info} + ) + }, + ).run() add_permission_view_menu.assert_not_called() database_needs_oauth2.purge_oauth2_tokens.assert_called() + + +def test_remove_oauth_config_purges_tokens( + mocker: MockerFixture, + database_needs_oauth2: MockerFixture, +) -> None: + """ + Test that removing the OAuth config from a database purges existing tokens. + """ + DatabaseDAO = mocker.patch("superset.commands.database.update.DatabaseDAO") # noqa: N806 + DatabaseDAO.find_by_id.return_value = database_needs_oauth2 + DatabaseDAO.update.return_value = database_needs_oauth2 + + find_permission_view_menu = mocker.patch.object( + security_manager, + "find_permission_view_menu", + ) + find_permission_view_menu.side_effect = [ + None, + "[my_db].[schema2]", + ] + add_permission_view_menu = mocker.patch.object( + security_manager, + "add_permission_view_menu", + ) + + UpdateDatabaseCommand(1, {"masked_encrypted_extra": None}).run() + + add_permission_view_menu.assert_not_called() + database_needs_oauth2.purge_oauth2_tokens.assert_called() + + UpdateDatabaseCommand(1, {"masked_encrypted_extra": "{}"}).run() + + add_permission_view_menu.assert_not_called() + database_needs_oauth2.purge_oauth2_tokens.assert_called() + + +def test_update_oauth2_removes_masked_encrypted_extra_key( + mocker: MockerFixture, + database_needs_oauth2: MockerFixture, +) -> None: + """ + Test that the ``masked_encrypted_extra`` key is properly purged from the properties. + """ + DatabaseDAO = mocker.patch("superset.commands.database.update.DatabaseDAO") # noqa: N806 + DatabaseDAO.find_by_id.return_value = database_needs_oauth2 + DatabaseDAO.update.return_value = database_needs_oauth2 + + find_permission_view_menu = mocker.patch.object( + security_manager, + "find_permission_view_menu", + ) + find_permission_view_menu.side_effect = [ + None, + "[my_db].[schema2]", + ] + add_permission_view_menu = mocker.patch.object( + security_manager, + "add_permission_view_menu", + ) + + modified_oauth2_client_info = oauth2_client_info.copy() + modified_oauth2_client_info["scope"] = "scope-b" + + UpdateDatabaseCommand( + 1, + { + "masked_encrypted_extra": json.dumps( + {"oauth2_client_info": modified_oauth2_client_info} + ) + }, + ).run() + + add_permission_view_menu.assert_not_called() + database_needs_oauth2.purge_oauth2_tokens.assert_called() + DatabaseDAO.update.assert_called_with( + database_needs_oauth2, + { + "encrypted_extra": json.dumps( + {"oauth2_client_info": modified_oauth2_client_info} + ) + }, + ) + + +def test_update_other_fields_dont_affect_oauth( + mocker: MockerFixture, + database_needs_oauth2: MockerFixture, +) -> None: + """ + Test that not including ``masked_encrypted_extra`` in the payload does not + touch the OAuth config. + """ + DatabaseDAO = mocker.patch("superset.commands.database.update.DatabaseDAO") # noqa: N806 + DatabaseDAO.find_by_id.return_value = database_needs_oauth2 + DatabaseDAO.update.return_value = database_needs_oauth2 + + find_permission_view_menu = mocker.patch.object( + security_manager, + "find_permission_view_menu", + ) + find_permission_view_menu.side_effect = [ + None, + "[my_db].[schema2]", + ] + add_permission_view_menu = mocker.patch.object( + security_manager, + "add_permission_view_menu", + ) + + UpdateDatabaseCommand(1, {"database_name": "New DB name"}).run() + + add_permission_view_menu.assert_not_called() + database_needs_oauth2.purge_oauth2_tokens.assert_not_called() diff --git a/tests/unit_tests/db_engine_specs/test_base.py b/tests/unit_tests/db_engine_specs/test_base.py index 2644cd6e6f055..bbc3bb0edcefd 100644 --- a/tests/unit_tests/db_engine_specs/test_base.py +++ b/tests/unit_tests/db_engine_specs/test_base.py @@ -226,7 +226,7 @@ class NoLimitDBEngineSpec(BaseEngineSpec): # mock the database so we can compile the query database = mocker.MagicMock() - database.compile_sqla_query = lambda query: str( + database.compile_sqla_query = lambda query, catalog, schema: str( query.compile(dialect=sqlite.dialect()) ) diff --git a/tests/unit_tests/db_engine_specs/test_bigquery.py b/tests/unit_tests/db_engine_specs/test_bigquery.py index 458a6a0393e7d..c28ff0e46b49a 100644 --- a/tests/unit_tests/db_engine_specs/test_bigquery.py +++ b/tests/unit_tests/db_engine_specs/test_bigquery.py @@ -149,7 +149,7 @@ def test_select_star(mocker: MockerFixture) -> None: # mock the database so we can compile the query database = mocker.MagicMock() - database.compile_sqla_query = lambda query: str( + database.compile_sqla_query = lambda query, catalog, schema: str( query.compile(dialect=BigQueryDialect(), compile_kwargs={"literal_binds": True}) ) diff --git a/tests/unit_tests/models/core_test.py b/tests/unit_tests/models/core_test.py index 5bc3c86af657a..f399fc85240d4 100644 --- a/tests/unit_tests/models/core_test.py +++ b/tests/unit_tests/models/core_test.py @@ -17,13 +17,22 @@ # pylint: disable=import-outside-toplevel + from datetime import datetime import pytest from pytest_mock import MockerFixture +from sqlalchemy import ( + Column, + Integer, + MetaData, + select, + Table as SqlalchemyTable, +) from sqlalchemy.engine.reflection import Inspector from sqlalchemy.engine.url import make_url from sqlalchemy.orm.session import Session +from sqlalchemy.sql import Select from superset.connectors.sqla.models import SqlaTable, TableColumn from superset.errors import SupersetErrorType @@ -45,6 +54,29 @@ } +@pytest.fixture +def query() -> Select: + """ + A nested query fixture used to test query optimization. + """ + metadata = MetaData() + some_table = SqlalchemyTable( + "some_table", + metadata, + Column("a", Integer), + Column("b", Integer), + Column("c", Integer), + ) + + inner_select = select(some_table.c.a, some_table.c.b, some_table.c.c) + outer_select = select(inner_select.c.a, inner_select.c.b).where( + inner_select.c.a > 1, + inner_select.c.b == 2, + ) + + return outer_select + + def test_get_metrics(mocker: MockerFixture) -> None: """ Tests for ``get_metrics``. @@ -683,3 +715,56 @@ def test_purge_oauth2_tokens(session: Session) -> None: # make sure database was not deleted... just in case database = session.query(Database).filter_by(id=database1.id).one() assert database.name == "my_oauth2_db" + + +def test_compile_sqla_query_no_optimization(query: Select) -> None: + """ + Test the `compile_sqla_query` method. + """ + from superset.models.core import Database + + database = Database( + database_name="db", + sqlalchemy_uri="sqlite://", + ) + + space = " " + # + assert ( + database.compile_sqla_query(query, is_virtual=True) + == f"""SELECT anon_1.a, anon_1.b{space} +FROM (SELECT some_table.a AS a, some_table.b AS b, some_table.c AS c{space} +FROM some_table) AS anon_1{space} +WHERE anon_1.a > 1 AND anon_1.b = 2""" # noqa: S608 + ) + + +@with_feature_flags(OPTIMIZE_SQL=True) +def test_compile_sqla_query(query: Select) -> None: + """ + Test the `compile_sqla_query` method. + """ + from superset.models.core import Database + + database = Database( + database_name="db", + sqlalchemy_uri="sqlite://", + ) + + assert ( + database.compile_sqla_query(query, is_virtual=True) + == """SELECT + anon_1.a, + anon_1.b +FROM ( + SELECT + some_table.a AS a, + some_table.b AS b, + some_table.c AS c + FROM some_table + WHERE + some_table.a > 1 AND some_table.b = 2 +) AS anon_1 +WHERE + TRUE AND TRUE""" + ) diff --git a/tests/unit_tests/sql/parse_tests.py b/tests/unit_tests/sql/parse_tests.py index 5103ef12eccf2..2df24a1b3a8f9 100644 --- a/tests/unit_tests/sql/parse_tests.py +++ b/tests/unit_tests/sql/parse_tests.py @@ -301,7 +301,7 @@ def test_format_no_dialect() -> None: Test format with an engine that has no corresponding dialect. """ assert ( - SQLScript("SELECT col FROM t WHERE col NOT IN (1, 2)", "firebolt").format() + SQLScript("SELECT col FROM t WHERE col NOT IN (1, 2)", "dremio").format() == "SELECT col\nFROM t\nWHERE col NOT IN (1,\n 2)" ) @@ -311,7 +311,7 @@ def test_split_no_dialect() -> None: Test the statement split when the engine has no corresponding dialect. """ sql = "SELECT col FROM t WHERE col NOT IN (1, 2); SELECT * FROM t; SELECT foo" - statements = SQLScript(sql, "firebolt").statements + statements = SQLScript(sql, "dremio").statements assert len(statements) == 3 assert statements[0]._sql == "SELECT col FROM t WHERE col NOT IN (1, 2)" assert statements[1]._sql == "SELECT * FROM t" @@ -1070,3 +1070,97 @@ def test_is_mutating(engine: str) -> None: "with source as ( select 1 as one ) select * from source", engine=engine, ).is_mutating() + + +def test_optimize() -> None: + """ + Test that the `optimize` method works as expected. + + The SQL optimization only works with engines that have a corresponding dialect. + """ + sql = """ +SELECT anon_1.a, anon_1.b +FROM (SELECT some_table.a AS a, some_table.b AS b, some_table.c AS c +FROM some_table) AS anon_1 +WHERE anon_1.a > 1 AND anon_1.b = 2 + """ + + optimized = """SELECT + anon_1.a, + anon_1.b +FROM ( + SELECT + some_table.a AS a, + some_table.b AS b, + some_table.c AS c + FROM some_table + WHERE + some_table.a > 1 AND some_table.b = 2 +) AS anon_1 +WHERE + TRUE AND TRUE""" + + not_optimized = """ +SELECT anon_1.a, + anon_1.b +FROM + (SELECT some_table.a AS a, + some_table.b AS b, + some_table.c AS c + FROM some_table) AS anon_1 +WHERE anon_1.a > 1 + AND anon_1.b = 2""" + + assert SQLStatement(sql, "sqlite").optimize().format() == optimized + assert SQLStatement(sql, "dremio").optimize().format() == not_optimized + + +def test_firebolt() -> None: + """ + Test that Firebolt 3rd party dialect is registered correctly. + + We need a custom dialect for Firebolt because it parses `NOT col IN (1, 2)` as + `(NOT col) IN (1, 2)` instead of `NOT (col IN (1, 2))`, which will fail when `col` + is not a boolean. + + Note that `NOT col = 1` works as expected in Firebolt, parsing as `NOT (col = 1)`. + """ + sql = "SELECT col NOT IN (1, 2) FROM tbl" + assert ( + SQLStatement(sql, "firebolt").format() + == """ +SELECT + NOT ( + col IN (1, 2) + ) +FROM tbl + """.strip() + ) + + sql = "SELECT NOT col = 1 FROM tbl" + assert ( + SQLStatement(sql, "firebolt").format() + == """ +SELECT + NOT col = 1 +FROM tbl + """.strip() + ) + + +def test_firebolt_old() -> None: + """ + Test the dialect for the old Firebolt syntax. + """ + from superset.sql.dialects import FireboltOld + from superset.sql.parse import SQLGLOT_DIALECTS + + SQLGLOT_DIALECTS["firebolt"] = FireboltOld + + sql = "SELECT * FROM t1 UNNEST(col1 AS foo)" + assert ( + SQLStatement(sql, "firebolt").format() + == """SELECT + * +FROM t1 UNNEST(col1 AS foo)""" + ) diff --git a/tests/unit_tests/sql_parse_tests.py b/tests/unit_tests/sql_parse_tests.py index 3b44c1c2cf3c7..9c814a0f4221f 100644 --- a/tests/unit_tests/sql_parse_tests.py +++ b/tests/unit_tests/sql_parse_tests.py @@ -1507,7 +1507,7 @@ def get_rls_for_table( else candidate_table.table ) for left, right in zip( - candidate_table_name.split(".")[::-1], table.split(".")[::-1] + candidate_table_name.split(".")[::-1], table.split(".")[::-1], strict=False ): if left != right: return None @@ -1719,7 +1719,9 @@ def get_rls_for_table( Return the RLS ``condition`` if ``candidate`` matches ``table``. """ # compare ignoring schema - for left, right in zip(str(candidate).split(".")[::-1], table.split(".")[::-1]): + for left, right in zip( + str(candidate).split(".")[::-1], table.split(".")[::-1], strict=False + ): if left != right: return None return condition diff --git a/tests/unit_tests/utils/date_parser_tests.py b/tests/unit_tests/utils/date_parser_tests.py index 3837cf8ace517..61121a28a9d63 100644 --- a/tests/unit_tests/utils/date_parser_tests.py +++ b/tests/unit_tests/utils/date_parser_tests.py @@ -92,6 +92,46 @@ def test_get_since_until() -> None: expected = datetime(2016, 11, 6), datetime(2016, 11, 8) assert result == expected + result = get_since_until(" : now") + expected = None, datetime(2016, 11, 7, 9, 30, 10) + assert result == expected + + result = get_since_until(" : last 2 minutes") + expected = None, datetime(2016, 11, 7, 9, 28, 10) + assert result == expected + + result = get_since_until(" : prior 2 minutes") + expected = None, datetime(2016, 11, 7, 9, 28, 10) + assert result == expected + + result = get_since_until(" : next 2 minutes") + expected = None, datetime(2016, 11, 7, 9, 32, 10) + assert result == expected + + result = get_since_until("start of this month : ") + expected = datetime(2016, 11, 1), None + assert result == expected + + result = get_since_until("start of next month : ") + expected = datetime(2016, 12, 1), None + assert result == expected + + result = get_since_until("end of this month : ") + expected = datetime(2016, 11, 30), None + assert result == expected + + result = get_since_until("end of next month : ") + expected = datetime(2016, 12, 31), None + assert result == expected + + result = get_since_until("beginning of next year : ") + expected = datetime(2017, 1, 1), None + assert result == expected + + result = get_since_until("beginning of last year : ") + expected = datetime(2015, 1, 1), None + assert result == expected + result = get_since_until("2018-01-01T00:00:00 : 2018-12-31T23:59:59") expected = datetime(2018, 1, 1), datetime(2018, 12, 31, 23, 59, 59) assert result == expected
{k} - {hex} +

Theme Colors

+ + + + + {tones.map(tone => ( + + ))} + + + + {colorTypes.map(category => ( + + - + ); + })} - ); - })} + ))} +
+ Category + + {tone} +
+ {category} + {tones.map(tone => { + const color = colors[category][tone]; + return ( + + {color ? {color} : '-'} +
+

+ text.label: {colors.text.label} +

+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea + commodo consequat. +
+

+ text.help: {colors.text.help} +

+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea + commodo consequat. +
+

The supersetTheme object

+ +
{JSON.stringify(supersetTheme, null, 2)}
+
- )); + ); }; diff --git a/superset-frontend/plugins/legacy-plugin-chart-event-flow/CHANGELOG.md b/superset-frontend/plugins/legacy-plugin-chart-event-flow/CHANGELOG.md deleted file mode 100644 index c2cec75e664c7..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-event-flow/CHANGELOG.md +++ /dev/null @@ -1,51 +0,0 @@ - - -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -# [0.20.0](https://github.com/apache/superset/compare/v2021.41.0...v0.20.0) (2024-09-09) - -### Bug Fixes - -- **generic-chart-axes:** set x-axis if unset and ff is enabled ([#20107](https://github.com/apache/superset/issues/20107)) ([0b3d3dd](https://github.com/apache/superset/commit/0b3d3dd4caa7f4c31c1ba7229966a40ba0469e85)) - -### Features - -- **explore:** Frontend implementation of dataset creation from infobox ([#19855](https://github.com/apache/superset/issues/19855)) ([ba0c37d](https://github.com/apache/superset/commit/ba0c37d3df85b1af39404af1d578daeb0ff2d278)) - -# [0.19.0](https://github.com/apache/superset/compare/v2021.41.0...v0.19.0) (2024-09-07) - -### Bug Fixes - -- **generic-chart-axes:** set x-axis if unset and ff is enabled ([#20107](https://github.com/apache/superset/issues/20107)) ([0b3d3dd](https://github.com/apache/superset/commit/0b3d3dd4caa7f4c31c1ba7229966a40ba0469e85)) - -### Features - -- **explore:** Frontend implementation of dataset creation from infobox ([#19855](https://github.com/apache/superset/issues/19855)) ([ba0c37d](https://github.com/apache/superset/commit/ba0c37d3df85b1af39404af1d578daeb0ff2d278)) - -# [0.18.0](https://github.com/apache-superset/superset-ui/compare/v0.17.87...v0.18.0) (2021-08-30) - -**Note:** Version bump only for package @superset-ui/legacy-plugin-chart-event-flow - -## [0.17.61](https://github.com/apache-superset/superset-ui/compare/v0.17.60...v0.17.61) (2021-07-02) - -**Note:** Version bump only for package @superset-ui/legacy-plugin-chart-event-flow diff --git a/superset-frontend/plugins/legacy-plugin-chart-event-flow/README.md b/superset-frontend/plugins/legacy-plugin-chart-event-flow/README.md deleted file mode 100644 index 4d6ab33568f6f..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-event-flow/README.md +++ /dev/null @@ -1,52 +0,0 @@ - - -## @superset-ui/legacy-plugin-chart-event-flow - -[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-event-flow.svg?style=flat)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-event-flow) -[![Libraries.io](https://img.shields.io/librariesio/release/npm/%40superset-ui%2Flegacy-plugin-chart-event-flow?style=flat)](https://libraries.io/npm/@superset-ui%2Flegacy-plugin-chart-event-flow) - -This plugin provides Event Flow for Superset. - -### Usage - -Configure `key`, which can be any `string`, and register the plugin. This `key` will be used to -lookup this chart throughout the app. - -```js -import EventFlowChartPlugin from '@superset-ui/legacy-plugin-chart-event-flow'; - -new EventFlowChartPlugin().configure({ key: 'event-flow' }).register(); -``` - -Then use it via `SuperChart`. See -[storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-event-flow) -for more details. - -```js - -``` diff --git a/superset-frontend/plugins/legacy-plugin-chart-event-flow/package.json b/superset-frontend/plugins/legacy-plugin-chart-event-flow/package.json deleted file mode 100644 index ead0d994fee9c..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-event-flow/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "@superset-ui/legacy-plugin-chart-event-flow", - "version": "0.20.3", - "description": "Superset Legacy Chart - Event Flow", - "sideEffects": [ - "*.css" - ], - "main": "lib/index.js", - "module": "esm/index.js", - "files": [ - "esm", - "lib" - ], - "repository": { - "type": "git", - "url": "https://github.com/apache/superset.git", - "directory": "superset-frontend/plugins/legacy-plugin-chart-event-flow" - }, - "keywords": [ - "superset" - ], - "author": "Superset", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/apache/superset/issues" - }, - "homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-event-flow#readme", - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@data-ui/event-flow": "^0.0.84", - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "@superset-ui/chart-controls": "*", - "@superset-ui/core": "*", - "react": "^15 || ^16" - } -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/controlPanel.tsx b/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/controlPanel.tsx deleted file mode 100644 index 32ed9969b3665..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/controlPanel.tsx +++ /dev/null @@ -1,133 +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 { t, validateNonEmpty } from '@superset-ui/core'; -import { - columnChoices, - ColumnOption, - ColumnMeta, - ControlPanelConfig, - ControlState, - formatSelectOptionsForRange, - SelectControlConfig, -} from '@superset-ui/chart-controls'; - -const config: ControlPanelConfig = { - controlPanelSections: [ - { - label: t('Event definition'), - controlSetRows: [ - ['entity'], - [ - { - name: 'all_columns_x', - config: { - type: 'SelectControl', - label: t('Event Names'), - description: t('Columns to display'), - mapStateToProps: state => ({ - choices: columnChoices(state?.datasource), - }), - // choices is from `mapStateToProps` - default: (control: ControlState) => - control.choices?.[0]?.[0] || null, - validators: [validateNonEmpty], - }, - }, - ], - ['row_limit'], - [ - { - name: 'order_by_entity', - config: { - type: 'CheckboxControl', - label: t('Order by entity id'), - description: t( - 'Important! Select this if the table is not already sorted by entity id, ' + - 'else there is no guarantee that all events for each entity are returned.', - ), - default: true, - }, - }, - ], - [ - { - name: 'min_leaf_node_event_count', - config: { - type: 'SelectControl', - freeForm: false, - label: t('Minimum leaf node event count'), - default: 1, - choices: formatSelectOptionsForRange(1, 10), - description: t( - 'Leaf nodes that represent fewer than this number of events will be initially ' + - 'hidden in the visualization', - ), - }, - }, - ], - ], - }, - { - label: t('Query'), - expanded: true, - controlSetRows: [['adhoc_filters']], - }, - { - label: t('Additional metadata'), - controlSetRows: [ - [ - { - name: 'all_columns', - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - config: { - type: 'SelectControl', - multi: true, - label: t('Metadata'), - default: [], - description: t('Select any columns for metadata inspection'), - optionRenderer: c => , - valueRenderer: c => , - valueKey: 'column_name', - allowAll: true, - mapStateToProps: state => ({ - options: state.datasource?.columns || [], - }), - commaChoosesOption: false, - freeForm: true, - } as SelectControlConfig, - }, - ], - ], - }, - ], - controlOverrides: { - entity: { - label: t('Entity ID'), - description: t('e.g., a "user id" column'), - }, - row_limit: { - label: t('Max Events'), - description: t( - 'The maximum number of events to return, equivalent to the number of rows', - ), - }, - }, -}; - -export default config; diff --git a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/images/example.jpg b/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/images/example.jpg deleted file mode 100644 index e2cfb02229a40..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/images/example.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/images/thumbnail.png b/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/images/thumbnail.png deleted file mode 100644 index de10a4d19beb8..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/images/thumbnail.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/images/thumbnailLarge.png b/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/images/thumbnailLarge.png deleted file mode 100644 index 0edb53563db28..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/images/thumbnailLarge.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/index.ts b/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/index.ts deleted file mode 100644 index 824f8f3dbacd3..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/index.ts +++ /dev/null @@ -1,46 +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 { t, ChartMetadata, ChartPlugin } from '@superset-ui/core'; -import thumbnail from './images/thumbnail.png'; -import example from './images/example.jpg'; -import controlPanel from './controlPanel'; - -const metadata = new ChartMetadata({ - category: t('Flow'), - credits: ['https://github.com/williaster/data-ui'], - description: t( - 'Compares the lengths of time different activities take in a shared timeline view.', - ), - exampleGallery: [{ url: example }], - name: t('Event Flow'), - tags: [t('Legacy'), t('Progressive')], - thumbnail, - useLegacyApi: true, -}); - -export default class EventFlowChartPlugin extends ChartPlugin { - constructor() { - super({ - loadChart: () => import('./EventFlow'), - loadTransformProps: () => import('./transformProps'), - metadata, - controlPanel, - }); - } -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/transformProps.ts b/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/transformProps.ts deleted file mode 100644 index c738ca6ebb4d7..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/transformProps.ts +++ /dev/null @@ -1,70 +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 { ChartProps, TimeseriesDataRecord } from '@superset-ui/core'; -import { cleanEvents, TS, EVENT_NAME, ENTITY_ID } from '@data-ui/event-flow'; - -export interface EventFlowFormData { - allColumnsX: string; - entity: string; - minLeafNodeEventCount: number; -} - -export interface EventFlowChartProps extends ChartProps { - formData: EventFlowFormData; - queriesData: { - data: TimeseriesDataRecord[]; - }[]; -} - -export default function transformProps(chartProps: ChartProps) { - const { formData, queriesData, width, height } = - chartProps as EventFlowChartProps; - const { allColumnsX, entity, minLeafNodeEventCount } = formData; - const { data } = queriesData[0]; - - const hasData = data && data.length > 0; - if (hasData) { - const userKey = entity; - const eventNameKey = allColumnsX; - - // map from the Superset form fields to 's expected data keys - const accessorFunctions = { - [ENTITY_ID]: (datum: TimeseriesDataRecord) => String(datum[userKey]), - [EVENT_NAME]: (datum: TimeseriesDataRecord) => - datum[eventNameKey] as string, - [TS]: (datum: TimeseriesDataRecord): Date | null => - // eslint-disable-next-line no-underscore-dangle - datum.__timestamp || datum.__timestamp === 0 - ? // eslint-disable-next-line no-underscore-dangle - new Date(datum.__timestamp) - : null, - }; - - const cleanData = cleanEvents(data, accessorFunctions); - - return { - data: cleanData, - height, - initialMinEventCount: minLeafNodeEventCount, - width, - }; - } - - return { data: null, height, width }; -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/types/external.d.ts b/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/types/external.d.ts deleted file mode 100644 index a9b451b3153fb..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/types/external.d.ts +++ /dev/null @@ -1,22 +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. - */ - -declare module '*.png'; -declare module '*.jpg'; -declare module '@data-ui/event-flow'; diff --git a/superset-frontend/plugins/legacy-plugin-chart-event-flow/tsconfig.json b/superset-frontend/plugins/legacy-plugin-chart-event-flow/tsconfig.json deleted file mode 100644 index b6bfaa2d98446..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-event-flow/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "declarationDir": "lib", - "outDir": "lib", - "rootDir": "src" - }, - "exclude": [ - "lib", - "test" - ], - "extends": "../../tsconfig.json", - "include": [ - "src/**/*", - "types/**/*", - "../../types/**/*" - ], - "references": [ - { - "path": "../../packages/superset-ui-chart-controls" - }, - { - "path": "../../packages/superset-ui-core" - } - ] -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/CHANGELOG.md b/superset-frontend/plugins/legacy-plugin-chart-heatmap/CHANGELOG.md deleted file mode 100644 index 1cedf14e9a8e3..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/CHANGELOG.md +++ /dev/null @@ -1,83 +0,0 @@ - - -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -# [0.20.0](https://github.com/apache/superset/compare/v2021.41.0...v0.20.0) (2024-09-09) - -### Bug Fixes - -- **chart & heatmap:** make to fix that y label is rendering out of bounds ([#20011](https://github.com/apache/superset/issues/20011)) ([56e9695](https://github.com/apache/superset/commit/56e96950c17ec65ef18cedfb2ed6591796a96cfc)) -- Date column in Heatmap is displayed as unix timestamp ([#25009](https://github.com/apache/superset/issues/25009)) ([35eb66a](https://github.com/apache/superset/commit/35eb66a322f7938f840778633a4aea11c7f24dce)) -- **explore:** Prevent shared controls from checking feature flags outside React render ([#21315](https://github.com/apache/superset/issues/21315)) ([2285ebe](https://github.com/apache/superset/commit/2285ebe72ec4edded6d195052740b7f9f13d1f1b)) -- Heatmap numeric sorting ([#27360](https://github.com/apache/superset/issues/27360)) ([fe2f5a7](https://github.com/apache/superset/commit/fe2f5a7be9fb6218aa72ab9173481fd21fa40b20)) -- **heatmap:** add detail descriptions for heatmap 'normalize across' ([#20566](https://github.com/apache/superset/issues/20566)) ([d925b0c](https://github.com/apache/superset/commit/d925b0c8835fb1773b80298a3de1bdc368c88850)) -- **legacy-plugin-chart-heatmap:** fix adhoc column tooltip ([#23507](https://github.com/apache/superset/issues/23507)) ([0cebe8b](https://github.com/apache/superset/commit/0cebe8bf18204d17f311345744e67c4bf5961083)) -- **select:** select component sort functionality on certain options ([#17638](https://github.com/apache/superset/issues/17638)) ([f476ba2](https://github.com/apache/superset/commit/f476ba23a279cb87a94ad3075e035cad0ae264b6)) -- Timeseries Y-axis format with contribution mode ([#27106](https://github.com/apache/superset/issues/27106)) ([af577d6](https://github.com/apache/superset/commit/af577d64b17a9730e28e9021376318326fe31437)) -- Tooltips don't disappear on the Heatmap chart ([#24959](https://github.com/apache/superset/issues/24959)) ([9703490](https://github.com/apache/superset/commit/97034901291420af844257fc76ac107d4a891f18)) - -### Features - -- Add currencies controls in control panels ([#24718](https://github.com/apache/superset/issues/24718)) ([f7e76d0](https://github.com/apache/superset/commit/f7e76d02b7cbe4940946673590bb979984ace9f5)) -- Adds the ECharts Heatmap chart ([#25353](https://github.com/apache/superset/issues/25353)) ([546d48a](https://github.com/apache/superset/commit/546d48adbb84b1354d6a3d4ae88dbeba0ad14d44)) -- **chart & legend:** make to enable show legend by default ([#19927](https://github.com/apache/superset/issues/19927)) ([7b3d0f0](https://github.com/apache/superset/commit/7b3d0f040b050905f7d0901d0227f1cd6b761b56)) -- **explore:** Apply denormalization to tier 2 charts form data ([#20524](https://github.com/apache/superset/issues/20524)) ([e12ee59](https://github.com/apache/superset/commit/e12ee59b13822241dca8d8015f1222c477edd4f3)) -- Implement support for currencies in more charts ([#24594](https://github.com/apache/superset/issues/24594)) ([d74d7ec](https://github.com/apache/superset/commit/d74d7eca23a3c94bc48af082c115d34c103e815d)) - -### Reverts - -- Revert "chore(deps): bump d3-svg-legend in /superset-frontend (#19846)" (#19972) ([f144de4](https://github.com/apache/superset/commit/f144de4ee2bf213bb7e17f903bd3975d504c4136)), closes [#19846](https://github.com/apache/superset/issues/19846) [#19972](https://github.com/apache/superset/issues/19972) - -# [0.19.0](https://github.com/apache/superset/compare/v2021.41.0...v0.19.0) (2024-09-07) - -### Bug Fixes - -- **chart & heatmap:** make to fix that y label is rendering out of bounds ([#20011](https://github.com/apache/superset/issues/20011)) ([56e9695](https://github.com/apache/superset/commit/56e96950c17ec65ef18cedfb2ed6591796a96cfc)) -- Date column in Heatmap is displayed as unix timestamp ([#25009](https://github.com/apache/superset/issues/25009)) ([35eb66a](https://github.com/apache/superset/commit/35eb66a322f7938f840778633a4aea11c7f24dce)) -- **explore:** Prevent shared controls from checking feature flags outside React render ([#21315](https://github.com/apache/superset/issues/21315)) ([2285ebe](https://github.com/apache/superset/commit/2285ebe72ec4edded6d195052740b7f9f13d1f1b)) -- Heatmap numeric sorting ([#27360](https://github.com/apache/superset/issues/27360)) ([fe2f5a7](https://github.com/apache/superset/commit/fe2f5a7be9fb6218aa72ab9173481fd21fa40b20)) -- **heatmap:** add detail descriptions for heatmap 'normalize across' ([#20566](https://github.com/apache/superset/issues/20566)) ([d925b0c](https://github.com/apache/superset/commit/d925b0c8835fb1773b80298a3de1bdc368c88850)) -- **legacy-plugin-chart-heatmap:** fix adhoc column tooltip ([#23507](https://github.com/apache/superset/issues/23507)) ([0cebe8b](https://github.com/apache/superset/commit/0cebe8bf18204d17f311345744e67c4bf5961083)) -- **select:** select component sort functionality on certain options ([#17638](https://github.com/apache/superset/issues/17638)) ([f476ba2](https://github.com/apache/superset/commit/f476ba23a279cb87a94ad3075e035cad0ae264b6)) -- Timeseries Y-axis format with contribution mode ([#27106](https://github.com/apache/superset/issues/27106)) ([af577d6](https://github.com/apache/superset/commit/af577d64b17a9730e28e9021376318326fe31437)) -- Tooltips don't disappear on the Heatmap chart ([#24959](https://github.com/apache/superset/issues/24959)) ([9703490](https://github.com/apache/superset/commit/97034901291420af844257fc76ac107d4a891f18)) - -### Features - -- Add currencies controls in control panels ([#24718](https://github.com/apache/superset/issues/24718)) ([f7e76d0](https://github.com/apache/superset/commit/f7e76d02b7cbe4940946673590bb979984ace9f5)) -- Adds the ECharts Heatmap chart ([#25353](https://github.com/apache/superset/issues/25353)) ([546d48a](https://github.com/apache/superset/commit/546d48adbb84b1354d6a3d4ae88dbeba0ad14d44)) -- **chart & legend:** make to enable show legend by default ([#19927](https://github.com/apache/superset/issues/19927)) ([7b3d0f0](https://github.com/apache/superset/commit/7b3d0f040b050905f7d0901d0227f1cd6b761b56)) -- **explore:** Apply denormalization to tier 2 charts form data ([#20524](https://github.com/apache/superset/issues/20524)) ([e12ee59](https://github.com/apache/superset/commit/e12ee59b13822241dca8d8015f1222c477edd4f3)) -- Implement support for currencies in more charts ([#24594](https://github.com/apache/superset/issues/24594)) ([d74d7ec](https://github.com/apache/superset/commit/d74d7eca23a3c94bc48af082c115d34c103e815d)) - -### Reverts - -- Revert "chore(deps): bump d3-svg-legend in /superset-frontend (#19846)" (#19972) ([f144de4](https://github.com/apache/superset/commit/f144de4ee2bf213bb7e17f903bd3975d504c4136)), closes [#19846](https://github.com/apache/superset/issues/19846) [#19972](https://github.com/apache/superset/issues/19972) - -# [0.18.0](https://github.com/apache-superset/superset-ui/compare/v0.17.87...v0.18.0) (2021-08-30) - -**Note:** Version bump only for package @superset-ui/legacy-plugin-chart-heatmap - -## [0.17.61](https://github.com/apache-superset/superset-ui/compare/v0.17.60...v0.17.61) (2021-07-02) - -**Note:** Version bump only for package @superset-ui/legacy-plugin-chart-heatmap diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/README.md b/superset-frontend/plugins/legacy-plugin-chart-heatmap/README.md deleted file mode 100644 index 2843c8f2ad7b9..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/README.md +++ /dev/null @@ -1,52 +0,0 @@ - - -## @superset-ui/legacy-plugin-chart-heatmap - -[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-heatmap.svg?style=flat)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-heatmap) -[![Libraries.io](https://img.shields.io/librariesio/release/npm/%40superset-ui%2Flegacy-plugin-chart-heatmap?style=flat)](https://libraries.io/npm/@superset-ui%2Flegacy-plugin-chart-heatmap) - -This plugin provides Heatmap for Superset. - -### Usage - -Configure `key`, which can be any `string`, and register the plugin. This `key` will be used to -lookup this chart throughout the app. - -```js -import HeatmapChartPlugin from '@superset-ui/legacy-plugin-chart-heatmap'; - -new HeatmapChartPlugin().configure({ key: 'heatmap' }).register(); -``` - -Then use it via `SuperChart`. See -[storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-heatmap) -for more details. - -```js - -``` diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/package.json b/superset-frontend/plugins/legacy-plugin-chart-heatmap/package.json deleted file mode 100644 index e6cc1a824f5ac..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "@superset-ui/legacy-plugin-chart-heatmap", - "version": "0.20.3", - "description": "Superset Legacy Chart - Heatmap", - "keywords": [ - "superset" - ], - "homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-heatmap#readme", - "bugs": { - "url": "https://github.com/apache/superset/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/apache/superset.git", - "directory": "superset-frontend/plugins/legacy-plugin-chart-heatmap" - }, - "license": "Apache-2.0", - "author": "Superset", - "main": "lib/index.js", - "module": "esm/index.js", - "files": [ - "esm", - "lib" - ], - "dependencies": { - "d3": "^3.5.17", - "d3-svg-legend": "^1.x", - "d3-tip": "^0.9.1", - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "@emotion/react": "^11.4.1", - "@superset-ui/chart-controls": "*", - "@superset-ui/core": "*", - "react": "^16.13.1" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js deleted file mode 100644 index ef2c76ad68a34..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js +++ /dev/null @@ -1,458 +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 func-names, react/sort-prop-types */ -import d3 from 'd3'; -import PropTypes from 'prop-types'; -import 'd3-svg-legend'; -import d3tip from 'd3-tip'; -import { - getColumnLabel, - getMetricLabel, - getNumberFormatter, - NumberFormats, - getSequentialSchemeRegistry, -} from '@superset-ui/core'; - -const propTypes = { - data: PropTypes.shape({ - records: PropTypes.arrayOf( - PropTypes.shape({ - x: PropTypes.string, - y: PropTypes.string, - v: PropTypes.number, - perc: PropTypes.number, - rank: PropTypes.number, - }), - ), - extents: PropTypes.arrayOf(PropTypes.number), - }), - width: PropTypes.number, - height: PropTypes.number, - bottomMargin: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - colorScheme: PropTypes.string, - columnX: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - columnY: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - leftMargin: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - metric: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - normalized: PropTypes.bool, - valueFormatter: PropTypes.object, - showLegend: PropTypes.bool, - showPercentage: PropTypes.bool, - showValues: PropTypes.bool, - sortXAxis: PropTypes.string, - sortYAxis: PropTypes.string, - xScaleInterval: PropTypes.number, - yScaleInterval: PropTypes.number, - yAxisBounds: PropTypes.arrayOf(PropTypes.number), -}; - -function cmp(a, b) { - return a > b ? 1 : -1; -} - -const DEFAULT_PROPERTIES = { - minChartWidth: 150, - minChartHeight: 150, - marginLeft: 35, - marginBottom: 35, - marginTop: 10, - marginRight: 10, -}; - -// Inspired from http://bl.ocks.org/mbostock/3074470 -// https://jsfiddle.net/cyril123/h0reyumq/ -function Heatmap(element, props) { - const { - data, - width, - height, - bottomMargin, - canvasImageRendering, - colorScheme, - columnX, - columnY, - leftMargin, - metric, - normalized, - valueFormatter, - showLegend, - showPercentage, - showValues, - sortXAxis, - sortYAxis, - xScaleInterval, - yScaleInterval, - yAxisBounds, - xAxisFormatter, - yAxisFormatter, - } = props; - - const { extents } = data; - const records = data.records.map(record => ({ - ...record, - x: xAxisFormatter(record.x), - y: yAxisFormatter(record.y), - })); - - const margin = { - top: 10, - right: 10, - bottom: 35, - left: 35, - }; - - let showY = true; - let showX = true; - const pixelsPerCharX = 4.5; // approx, depends on font size - let pixelsPerCharY = 6; // approx, depends on font size - - // Dynamically adjusts based on max x / y category lengths - function adjustMargins() { - let longestX = 1; - let longestY = 1; - - records.forEach(datum => { - if (typeof datum.y === 'number') pixelsPerCharY = 7; - longestX = Math.max( - longestX, - (datum.x && datum.x.toString().length) || 1, - ); - longestY = Math.max( - longestY, - (datum.y && datum.y.toString().length) || 1, - ); - }); - - if (leftMargin === 'auto') { - margin.left = Math.ceil(Math.max(margin.left, pixelsPerCharY * longestY)); - } else { - margin.left = leftMargin; - } - - if (showLegend) { - margin.right += 40; - } - - margin.bottom = - bottomMargin === 'auto' - ? Math.ceil(Math.max(margin.bottom, pixelsPerCharX * longestX)) - : bottomMargin; - } - - // Check if x axis "x" position is outside of the container and rotate labels 90deg - function checkLabelPosition(container) { - const xAxisNode = container.select('.x.axis').node(); - - if (!xAxisNode) { - return; - } - - if ( - xAxisNode.getBoundingClientRect().x + 4 < - container.node().getBoundingClientRect().x - ) { - container - .selectAll('.x.axis') - .selectAll('text') - .attr('transform', 'rotate(-90)') - .attr('x', -6) - .attr('y', 0) - .attr('dy', '0.3em'); - } - } - - function ordScale(k, rangeBands, sortMethod, formatter) { - let domain = {}; - records.forEach(d => { - domain[d[k]] = (domain[d[k]] || 0) + d.v; - }); - const keys = Object.keys(domain).map(k => formatter(k)); - if (sortMethod === 'alpha_asc') { - domain = keys.sort(cmp); - } else if (sortMethod === 'alpha_desc') { - domain = keys.sort(cmp).reverse(); - } else if (sortMethod === 'value_desc') { - domain = Object.keys(domain).sort((a, b) => - domain[a] > domain[b] ? -1 : 1, - ); - } else if (sortMethod === 'value_asc') { - domain = Object.keys(domain).sort((a, b) => - domain[b] > domain[a] ? -1 : 1, - ); - } - - if (k === 'y' && rangeBands) { - domain.reverse(); - } - - if (rangeBands) { - return d3.scale.ordinal().domain(domain).rangeBands(rangeBands); - } - - return d3.scale.ordinal().domain(domain).range(d3.range(domain.length)); - } - - // eslint-disable-next-line no-param-reassign - element.innerHTML = ''; - const matrix = {}; - - adjustMargins(); - - let hmWidth = width - (margin.left + margin.right); - let hmHeight = height - (margin.bottom + margin.top); - const hideYLabel = () => { - margin.left = - leftMargin === 'auto' ? DEFAULT_PROPERTIES.marginLeft : leftMargin; - hmWidth = width - (margin.left + margin.right); - showY = false; - }; - - const hideXLabel = () => { - margin.bottom = - bottomMargin === 'auto' ? DEFAULT_PROPERTIES.marginBottom : bottomMargin; - hmHeight = height - (margin.bottom + margin.top); - showX = false; - }; - - // Hide Y Labels - if (hmWidth < DEFAULT_PROPERTIES.minChartWidth) { - hideYLabel(); - } - - // Hide X Labels - if ( - hmHeight < DEFAULT_PROPERTIES.minChartHeight || - hmWidth < DEFAULT_PROPERTIES.minChartWidth - ) { - hideXLabel(); - } - - if (showY && hmHeight < DEFAULT_PROPERTIES.minChartHeight) { - hideYLabel(); - } - - const fp = getNumberFormatter(NumberFormats.PERCENT_2_POINT); - - const xScale = ordScale('x', null, sortXAxis, xAxisFormatter); - const yScale = ordScale('y', null, sortYAxis, yAxisFormatter); - const xRbScale = ordScale('x', [0, hmWidth], sortXAxis, xAxisFormatter); - const yRbScale = ordScale('y', [hmHeight, 0], sortYAxis, yAxisFormatter); - const X = 0; - const Y = 1; - const heatmapDim = [xRbScale.domain().length, yRbScale.domain().length]; - - const minBound = yAxisBounds[0] || 0; - const maxBound = yAxisBounds[1] || 1; - const colorScale = getSequentialSchemeRegistry() - .get(colorScheme) - .createLinearScale([minBound, maxBound]); - - const scale = [ - d3.scale.linear().domain([0, heatmapDim[X]]).range([0, hmWidth]), - d3.scale.linear().domain([0, heatmapDim[Y]]).range([0, hmHeight]), - ]; - - const container = d3.select(element); - container.classed('superset-legacy-chart-heatmap', true); - - const canvas = container - .append('canvas') - .attr('width', heatmapDim[X]) - .attr('height', heatmapDim[Y]) - .style('width', `${hmWidth}px`) - .style('height', `${hmHeight}px`) - .style('image-rendering', canvasImageRendering) - .style('left', `${margin.left}px`) - .style('top', `${margin.top}px`) - .style('position', 'absolute'); - - const svg = container - .append('svg') - .attr('width', width) - .attr('height', height) - .attr('class', 'heatmap-container') - .style('position', 'relative'); - - if (showValues) { - const cells = svg - .selectAll('rect') - .data(records) - .enter() - .append('g') - .attr('transform', `translate(${margin.left}, ${margin.top})`); - - cells - .append('text') - .attr('transform', d => `translate(${xRbScale(d.x)}, ${yRbScale(d.y)})`) - .attr('y', yRbScale.rangeBand() / 2) - .attr('x', xRbScale.rangeBand() / 2) - .attr('text-anchor', 'middle') - .attr('dy', '.35em') - .text(d => valueFormatter(d.v)) - .attr( - 'font-size', - `${Math.min(yRbScale.rangeBand(), xRbScale.rangeBand()) / 3}px`, - ) - .attr('fill', d => (d.v >= extents[1] / 2 ? 'white' : 'black')); - } - - if (showLegend) { - const colorLegend = d3.legend - .color() - .labelFormat(valueFormatter) - .scale(colorScale) - .shapePadding(0) - .cells(10) - .shapeWidth(10) - .shapeHeight(10) - .labelOffset(3); - - svg - .append('g') - .attr('transform', `translate(${width - 40}, ${margin.top})`) - .call(colorLegend); - } - - const tip = d3tip() - .attr('class', 'd3-tip') - .offset(function () { - const k = d3.mouse(this); - const x = k[0] - hmWidth / 2; - - return [k[1] - 20, x]; - }) - .html(function () { - let s = ''; - const k = d3.mouse(this); - const m = Math.floor(scale[0].invert(k[0])); - const n = Math.floor(scale[1].invert(k[1])); - if (m in matrix && n in matrix[m]) { - const obj = matrix[m][n]; - s += `
${getColumnLabel(columnX)}: ${obj.x}
`; - s += `
${getColumnLabel(columnY)}: ${obj.y}
`; - s += `
${getMetricLabel(metric)}: ${valueFormatter( - obj.v, - )}
`; - if (showPercentage) { - s += `
%: ${fp(normalized ? obj.rank : obj.perc)}
`; - } - tip.style('display', null); - } else { - // this is a hack to hide the tooltip because we have map it to a single - // d3-tip toggles opacity and calling hide here is undone by the lib after this call - tip.style('display', 'none'); - } - - return s; - }); - - const rect = svg - .append('g') - .attr('transform', `translate(${margin.left}, ${margin.top})`) - .append('rect') - .classed('background-rect', true) - .on('mousemove', tip.show) - .on('mouseout', tip.hide) - .attr('width', hmWidth) - .attr('height', hmHeight); - - rect.call(tip); - - if (showX) { - const xAxis = d3.svg - .axis() - .scale(xRbScale) - .outerTickSize(0) - .tickValues(xRbScale.domain().filter((d, i) => !(i % xScaleInterval))) - .orient('bottom'); - - svg - .append('g') - .attr('class', 'x axis') - .attr('transform', `translate(${margin.left},${margin.top + hmHeight})`) - .call(xAxis) - .selectAll('text') - .attr('x', -4) - .attr('y', 10) - .attr('dy', '0.3em') - .style('text-anchor', 'end') - .attr('transform', 'rotate(-45)'); - } - - if (showY) { - const yAxis = d3.svg - .axis() - .scale(yRbScale) - .outerTickSize(0) - .tickValues(yRbScale.domain().filter((d, i) => !(i % yScaleInterval))) - .orient('left'); - - svg - .append('g') - .attr('class', 'y axis') - .attr('transform', `translate(${margin.left},${margin.top})`) - .call(yAxis); - } - - checkLabelPosition(container); - const context = canvas.node().getContext('2d'); - context.imageSmoothingEnabled = false; - - // Compute the pixel colors; scaled by CSS. - function createImageObj() { - const imageObj = new Image(); - const image = context.createImageData(heatmapDim[0], heatmapDim[1]); - const pixs = {}; - records.forEach(d => { - const c = d3.rgb(colorScale(normalized ? d.rank : d.perc)); - const x = xScale(d.x); - const y = yScale(d.y); - pixs[x + y * xScale.domain().length] = c; - if (matrix[x] === undefined) { - matrix[x] = {}; - } - if (matrix[x][y] === undefined) { - matrix[x][y] = d; - } - }); - - let p = 0; - for (let i = 0; i < heatmapDim[0] * heatmapDim[1]; i += 1) { - let c = pixs[i]; - let alpha = 255; - if (c === undefined) { - c = d3.rgb('#F00'); - alpha = 0; - } - image.data[p + 0] = c.r; - image.data[p + 1] = c.g; - image.data[p + 2] = c.b; - image.data[p + 3] = alpha; - p += 4; - } - context.putImageData(image, 0, 0); - imageObj.src = canvas.node().toDataURL(); - } - createImageObj(); -} - -Heatmap.displayName = 'Heatmap'; -Heatmap.propTypes = propTypes; - -export default Heatmap; diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/ReactHeatmap.jsx b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/ReactHeatmap.jsx deleted file mode 100644 index 9dad51e5242a1..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/ReactHeatmap.jsx +++ /dev/null @@ -1,152 +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 { reactify, css, styled } from '@superset-ui/core'; -import { Global } from '@emotion/react'; -import Component from './Heatmap'; - -function componentWillUnmount() { - // Removes tooltips from the DOM - document.querySelectorAll('.d3-tip').forEach(t => t.remove()); -} - -const ReactComponent = reactify(Component, { componentWillUnmount }); - -const Heatmap = ({ className, ...otherProps }) => ( -
- css` - .d3-tip { - line-height: 1; - padding: ${theme.gridUnit * 3}px; - background: ${theme.colors.grayscale.dark2}; - color: ${theme.colors.grayscale.light5}; - border-radius: 4px; - pointer-events: none; - z-index: 1000; - font-size: ${theme.typography.sizes.s}px; - } - - /* Creates a small triangle extender for the tooltip */ - .d3-tip:after { - box-sizing: border-box; - display: inline; - font-size: ${theme.typography.sizes.xs}; - width: 100%; - line-height: 1; - color: ${theme.colors.grayscale.dark2}; - position: absolute; - pointer-events: none; - } - - /* Northward tooltips */ - .d3-tip.n:after { - content: '\\25BC'; - margin: -${theme.gridUnit}px 0 0 0; - top: 100%; - left: 0; - text-align: center; - } - - /* Eastward tooltips */ - .d3-tip.e:after { - content: '\\25C0'; - margin: -${theme.gridUnit}px 0 0 0; - top: 50%; - left: -${theme.gridUnit * 2}px; - } - - /* Southward tooltips */ - .d3-tip.s:after { - content: '\\25B2'; - margin: 0; - top: -${theme.gridUnit * 2}px; - left: 0; - text-align: center; - } - - /* Westward tooltips */ - .d3-tip.w:after { - content: '\\25B6'; - margin: -${theme.gridUnit}px 0 0 0px; - top: 50%; - left: 100%; - } - `} - /> - -
-); - -export default styled(Heatmap)` - ${({ theme }) => ` - .superset-legacy-chart-heatmap { - position: relative; - top: 0; - left: 0; - height: 100%; - } - - .superset-legacy-chart-heatmap .axis text { - font-size: ${theme.typography.sizes.xs}px; - text-rendering: optimizeLegibility; - } - - .superset-legacy-chart-heatmap .background-rect { - stroke: ${theme.colors.grayscale.light2}; - fill-opacity: 0; - pointer-events: all; - } - - .superset-legacy-chart-heatmap .axis path, - .superset-legacy-chart-heatmap .axis line { - fill: none; - stroke: ${theme.colors.grayscale.light2}; - shape-rendering: crispEdges; - } - - .superset-legacy-chart-heatmap canvas, - .superset-legacy-chart-heatmap img { - image-rendering: optimizeSpeed; /* Older versions of FF */ - image-rendering: -moz-crisp-edges; /* FF 6.0+ */ - image-rendering: -webkit-optimize-contrast; /* Safari */ - image-rendering: -o-crisp-edges; /* OS X & Windows Opera (12.02+) */ - image-rendering: pixelated; /* Awesome future-browsers */ - -ms-interpolation-mode: nearest-neighbor; /* IE */ - } - - .superset-legacy-chart-heatmap .legendCells text { - font-size: ${theme.typography.sizes.xs}px; - font-weight: ${theme.typography.weights.normal}; - opacity: 0; - } - - .superset-legacy-chart-heatmap .legendCells .cell:first-child text { - opacity: 1; - } - - .superset-legacy-chart-heatmap .legendCells .cell:last-child text { - opacity: 1; - } - - .dashboard .superset-legacy-chart-heatmap .axis text { - font-size: ${theme.typography.sizes.xs}px; - opacity: ${theme.opacity.heavy}; - } - `} -`; diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx deleted file mode 100644 index d22428e1ea844..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx +++ /dev/null @@ -1,329 +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 { t } from '@superset-ui/core'; -import { - ControlPanelConfig, - formatSelectOptionsForRange, - sharedControls, - getStandardizedControls, - D3_TIME_FORMAT_DOCS, -} from '@superset-ui/chart-controls'; - -const sortAxisChoices = [ - ['alpha_asc', t('Axis ascending')], - ['alpha_desc', t('Axis descending')], - ['value_asc', t('Metric ascending')], - ['value_desc', t('Metric descending')], -]; - -const dndAllColumns = { - ...sharedControls.entity, - description: t('Columns to display'), -}; - -const config: ControlPanelConfig = { - controlPanelSections: [ - { - label: t('Query'), - expanded: true, - controlSetRows: [ - [ - { - name: 'all_columns_x', - config: { - ...dndAllColumns, - label: t('X Axis'), - }, - }, - ], - [ - { - name: 'all_columns_y', - config: { - ...dndAllColumns, - label: t('Y Axis'), - }, - }, - ], - ['metric'], - ['adhoc_filters'], - ['row_limit'], - ['sort_by_metric'], - ], - }, - { - label: t('Heatmap Options'), - expanded: true, - tabOverride: 'customize', - controlSetRows: [ - ['linear_color_scheme'], - [ - { - name: 'xscale_interval', - config: { - type: 'SelectControl', - label: t('XScale Interval'), - renderTrigger: true, - choices: formatSelectOptionsForRange(1, 50), - default: 1, - clearable: false, - description: t( - 'Number of steps to take between ticks when displaying the X scale', - ), - }, - }, - ], - [ - { - name: 'yscale_interval', - config: { - type: 'SelectControl', - label: t('YScale Interval'), - choices: formatSelectOptionsForRange(1, 50), - default: 1, - clearable: false, - renderTrigger: true, - description: t( - 'Number of steps to take between ticks when displaying the Y scale', - ), - }, - }, - ], - [ - { - name: 'canvas_image_rendering', - config: { - type: 'SelectControl', - label: t('Rendering'), - renderTrigger: true, - choices: [ - ['pixelated', t('pixelated (Sharp)')], - ['auto', t('auto (Smooth)')], - ], - default: 'pixelated', - description: t( - 'image-rendering CSS attribute of the canvas object that ' + - 'defines how the browser scales up the image', - ), - }, - }, - ], - [ - { - name: 'normalize_across', - config: { - type: 'SelectControl', - label: t('Normalize Across'), - choices: [ - ['heatmap', t('heatmap')], - ['x', t('x')], - ['y', t('y')], - ], - default: 'heatmap', - description: ( - <> -
- {t( - 'Color will be shaded based the normalized (0% to 100%) value of a given cell against the other cells in the selected range: ', - )} -
-
    -
  • {t('x: values are normalized within each column')}
  • -
  • {t('y: values are normalized within each row')}
  • -
  • - {t( - 'heatmap: values are normalized across the entire heatmap', - )} -
  • -
- - ), - }, - }, - ], - [ - { - name: 'left_margin', - config: { - type: 'SelectControl', - freeForm: true, - clearable: false, - label: t('Left Margin'), - choices: [ - ['auto', t('auto')], - [50, '50'], - [75, '75'], - [100, '100'], - [125, '125'], - [150, '150'], - [200, '200'], - ], - default: 'auto', - renderTrigger: true, - description: t( - 'Left margin, in pixels, allowing for more room for axis labels', - ), - }, - }, - ], - [ - { - name: 'bottom_margin', - config: { - type: 'SelectControl', - clearable: false, - freeForm: true, - label: t('Bottom Margin'), - choices: [ - ['auto', t('auto')], - [50, '50'], - [75, '75'], - [100, '100'], - [125, '125'], - [150, '150'], - [200, '200'], - ], - default: 'auto', - renderTrigger: true, - description: t( - 'Bottom margin, in pixels, allowing for more room for axis labels', - ), - }, - }, - ], - [ - { - name: 'y_axis_bounds', - config: { - type: 'BoundsControl', - label: t('Value bounds'), - renderTrigger: true, - default: [null, null], - description: t( - 'Hard value bounds applied for color coding. Is only relevant ' + - 'and applied when the normalization is applied against the whole heatmap.', - ), - }, - }, - ], - ['y_axis_format'], - [ - { - name: 'time_format', - config: { - ...sharedControls.x_axis_time_format, - default: '%d/%m/%Y', - description: `${D3_TIME_FORMAT_DOCS}.`, - }, - }, - ], - ['currency_format'], - [ - { - name: 'sort_x_axis', - config: { - type: 'SelectControl', - label: t('Sort X Axis'), - choices: sortAxisChoices, - clearable: false, - default: 'alpha_asc', - }, - }, - ], - [ - { - name: 'sort_y_axis', - config: { - type: 'SelectControl', - label: t('Sort Y Axis'), - choices: sortAxisChoices, - clearable: false, - default: 'alpha_asc', - }, - }, - ], - [ - { - name: 'show_legend', - config: { - type: 'CheckboxControl', - label: t('Legend'), - renderTrigger: true, - default: true, - description: t('Whether to display the legend (toggles)'), - }, - }, - ], - [ - { - name: 'show_perc', - config: { - type: 'CheckboxControl', - label: t('Show percentage'), - renderTrigger: true, - description: t( - 'Whether to include the percentage in the tooltip', - ), - default: true, - }, - }, - ], - [ - { - name: 'show_values', - config: { - type: 'CheckboxControl', - label: t('Show Values'), - renderTrigger: true, - default: false, - description: t( - 'Whether to display the numerical values within the cells', - ), - }, - }, - ], - [ - { - name: 'normalized', - config: { - type: 'CheckboxControl', - label: t('Normalized'), - renderTrigger: true, - description: t( - 'Whether to apply a normal distribution based on rank on the color scale', - ), - default: false, - }, - }, - ], - ], - }, - ], - controlOverrides: { - y_axis_format: { - label: t('Value Format'), - }, - }, - formDataOverrides: formData => ({ - ...formData, - metric: getStandardizedControls().shiftMetric(), - }), -}; - -export default config; diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/channels.jpg b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/channels.jpg deleted file mode 100644 index 8f6c1b3e967e0..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/channels.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/employment.jpg b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/employment.jpg deleted file mode 100644 index 1a55ef839c13f..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/employment.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/thumbnail.png b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/thumbnail.png deleted file mode 100644 index f1703fc9d9f8e..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/thumbnail.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/thumbnailLarge.png b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/thumbnailLarge.png deleted file mode 100644 index 1dfeabbdbb1b2..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/thumbnailLarge.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/transportation.jpg b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/transportation.jpg deleted file mode 100644 index 1cf7a89cba2bf..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/images/transportation.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/index.js b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/index.js deleted file mode 100644 index 3779c0d03ecd0..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/index.js +++ /dev/null @@ -1,65 +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 { t, ChartMetadata, ChartPlugin, ChartLabel } from '@superset-ui/core'; -import transformProps from './transformProps'; -import transportation from './images/transportation.jpg'; -import channels from './images/channels.jpg'; -import employment from './images/employment.jpg'; -import thumbnail from './images/thumbnail.png'; -import controlPanel from './controlPanel'; - -const metadata = new ChartMetadata({ - category: t('Correlation'), - credits: ['http://bl.ocks.org/mbostock/3074470'], - description: t( - 'Visualize a related metric across pairs of groups. Heatmaps excel at showcasing the correlation or strength between two groups. Color is used to emphasize the strength of the link between each pair of groups.', - ), - exampleGallery: [ - { url: transportation, caption: t('Sizes of vehicles') }, - { url: channels, caption: t('Relationships between community channels') }, - { url: employment, caption: t('Employment and education') }, - ], - label: ChartLabel.DEPRECATED, - name: t('Heatmap (legacy)'), - tags: [ - t('Business'), - t('Intensity'), - t('Legacy'), - t('Density'), - t('Predictive'), - t('Single Metric'), - t('Deprecated'), - ], - thumbnail, - useLegacyApi: true, -}); - -/** - * @deprecated in version 4.0. - */ -export default class HeatmapChartPlugin extends ChartPlugin { - constructor() { - super({ - loadChart: () => import('./ReactHeatmap'), - metadata, - transformProps, - controlPanel, - }); - } -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/transformProps.js b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/transformProps.js deleted file mode 100644 index 8b925f2974053..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/transformProps.js +++ /dev/null @@ -1,93 +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 { - GenericDataType, - getTimeFormatter, - getValueFormatter, -} from '@superset-ui/core'; - -export default function transformProps(chartProps) { - const { width, height, formData, queriesData, datasource } = chartProps; - const { - bottomMargin, - canvasImageRendering, - allColumnsX, - allColumnsY, - linearColorScheme, - leftMargin, - metric, - normalized, - showLegend, - showPerc, - showValues, - sortXAxis, - sortYAxis, - xscaleInterval, - yscaleInterval, - yAxisBounds, - yAxisFormat, - timeFormat, - currencyFormat, - } = formData; - const { data = [], coltypes = [] } = queriesData[0]; - const { columnFormats = {}, currencyFormats = {} } = datasource; - const valueFormatter = getValueFormatter( - metric, - currencyFormats, - columnFormats, - yAxisFormat, - currencyFormat, - ); - const xAxisFormatter = - coltypes[0] === GenericDataType.Temporal - ? getTimeFormatter(timeFormat) - : coltypes[0] === GenericDataType.Numeric - ? Number - : String; - const yAxisFormatter = - coltypes[1] === GenericDataType.Temporal - ? getTimeFormatter(timeFormat) - : coltypes[1] === GenericDataType.Numeric - ? Number - : String; - return { - width, - height, - data, - bottomMargin, - canvasImageRendering, - colorScheme: linearColorScheme, - columnX: allColumnsX, - columnY: allColumnsY, - leftMargin, - metric, - normalized, - showLegend, - showPercentage: showPerc, - showValues, - sortXAxis, - sortYAxis, - xScaleInterval: parseInt(xscaleInterval, 10), - yScaleInterval: parseInt(yscaleInterval, 10), - yAxisBounds, - valueFormatter, - xAxisFormatter, - yAxisFormatter, - }; -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/tsconfig.json b/superset-frontend/plugins/legacy-plugin-chart-heatmap/tsconfig.json deleted file mode 100644 index b6bfaa2d98446..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "declarationDir": "lib", - "outDir": "lib", - "rootDir": "src" - }, - "exclude": [ - "lib", - "test" - ], - "extends": "../../tsconfig.json", - "include": [ - "src/**/*", - "types/**/*", - "../../types/**/*" - ], - "references": [ - { - "path": "../../packages/superset-ui-chart-controls" - }, - { - "path": "../../packages/superset-ui-core" - } - ] -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/CHANGELOG.md b/superset-frontend/plugins/legacy-plugin-chart-histogram/CHANGELOG.md deleted file mode 100644 index 93130f5dc5f5b..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-histogram/CHANGELOG.md +++ /dev/null @@ -1,63 +0,0 @@ - - -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -# [0.20.0](https://github.com/apache/superset/compare/v2021.41.0...v0.20.0) (2024-09-09) - -### Bug Fixes - -- **Dashboard:** Color inconsistency on refreshes and conflicts ([#27439](https://github.com/apache/superset/issues/27439)) ([313ee59](https://github.com/apache/superset/commit/313ee596f5435894f857d72be7269d5070c8c964)) -- **explore:** Prevent shared controls from checking feature flags outside React render ([#21315](https://github.com/apache/superset/issues/21315)) ([2285ebe](https://github.com/apache/superset/commit/2285ebe72ec4edded6d195052740b7f9f13d1f1b)) -- **histogram:** display correct percentile value instead of formula ([#18084](https://github.com/apache/superset/issues/18084)) ([28e729b](https://github.com/apache/superset/commit/28e729b835d8195f3610f7131504441803e43406)) -- Revert shared controls typing change. ([#22014](https://github.com/apache/superset/issues/22014)) ([4cbd70d](https://github.com/apache/superset/commit/4cbd70db34b140a026ef1a86a8ef0ba3355a350e)) - -### Features - -- Adds the ECharts Histogram chart ([#28652](https://github.com/apache/superset/issues/28652)) ([896fe85](https://github.com/apache/superset/commit/896fe854dc3865214325cfceea94824ff41a1b6c)) -- **chart & legend:** make to enable show legend by default ([#19927](https://github.com/apache/superset/issues/19927)) ([7b3d0f0](https://github.com/apache/superset/commit/7b3d0f040b050905f7d0901d0227f1cd6b761b56)) -- **explore:** Apply denormalization to tier 2 charts form data ([#20524](https://github.com/apache/superset/issues/20524)) ([e12ee59](https://github.com/apache/superset/commit/e12ee59b13822241dca8d8015f1222c477edd4f3)) -- improve color consistency (save all labels) ([#19038](https://github.com/apache/superset/issues/19038)) ([dc57508](https://github.com/apache/superset/commit/dc575080d7e43d40b1734bb8f44fdc291cb95b11)) - -# [0.19.0](https://github.com/apache/superset/compare/v2021.41.0...v0.19.0) (2024-09-07) - -### Bug Fixes - -- **Dashboard:** Color inconsistency on refreshes and conflicts ([#27439](https://github.com/apache/superset/issues/27439)) ([313ee59](https://github.com/apache/superset/commit/313ee596f5435894f857d72be7269d5070c8c964)) -- **explore:** Prevent shared controls from checking feature flags outside React render ([#21315](https://github.com/apache/superset/issues/21315)) ([2285ebe](https://github.com/apache/superset/commit/2285ebe72ec4edded6d195052740b7f9f13d1f1b)) -- **histogram:** display correct percentile value instead of formula ([#18084](https://github.com/apache/superset/issues/18084)) ([28e729b](https://github.com/apache/superset/commit/28e729b835d8195f3610f7131504441803e43406)) -- Revert shared controls typing change. ([#22014](https://github.com/apache/superset/issues/22014)) ([4cbd70d](https://github.com/apache/superset/commit/4cbd70db34b140a026ef1a86a8ef0ba3355a350e)) - -### Features - -- Adds the ECharts Histogram chart ([#28652](https://github.com/apache/superset/issues/28652)) ([896fe85](https://github.com/apache/superset/commit/896fe854dc3865214325cfceea94824ff41a1b6c)) -- **chart & legend:** make to enable show legend by default ([#19927](https://github.com/apache/superset/issues/19927)) ([7b3d0f0](https://github.com/apache/superset/commit/7b3d0f040b050905f7d0901d0227f1cd6b761b56)) -- **explore:** Apply denormalization to tier 2 charts form data ([#20524](https://github.com/apache/superset/issues/20524)) ([e12ee59](https://github.com/apache/superset/commit/e12ee59b13822241dca8d8015f1222c477edd4f3)) -- improve color consistency (save all labels) ([#19038](https://github.com/apache/superset/issues/19038)) ([dc57508](https://github.com/apache/superset/commit/dc575080d7e43d40b1734bb8f44fdc291cb95b11)) - -# [0.18.0](https://github.com/apache-superset/superset-ui/compare/v0.17.87...v0.18.0) (2021-08-30) - -**Note:** Version bump only for package @superset-ui/legacy-plugin-chart-histogram - -## [0.17.61](https://github.com/apache-superset/superset-ui/compare/v0.17.60...v0.17.61) (2021-07-02) - -**Note:** Version bump only for package @superset-ui/legacy-plugin-chart-histogram diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/README.md b/superset-frontend/plugins/legacy-plugin-chart-histogram/README.md deleted file mode 100644 index 7d02227b605cc..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-histogram/README.md +++ /dev/null @@ -1,52 +0,0 @@ - - -## @superset-ui/legacy-plugin-chart-histogram - -[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-histogram.svg?style=flat)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-histogram) -[![Libraries.io](https://img.shields.io/librariesio/release/npm/%40superset-ui%2Flegacy-plugin-chart-histogram?style=flat)](https://libraries.io/npm/@superset-ui%2Flegacy-plugin-chart-histogram) - -This plugin provides Histogram for Superset. - -### Usage - -Configure `key`, which can be any `string`, and register the plugin. This `key` will be used to -lookup this chart throughout the app. - -```js -import HistogramChartPlugin from '@superset-ui/legacy-plugin-chart-histogram'; - -new HistogramChartPlugin().configure({ key: 'histogram' }).register(); -``` - -Then use it via `SuperChart`. See -[storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-histogram) -for more details. - -```js - -``` diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/package.json b/superset-frontend/plugins/legacy-plugin-chart-histogram/package.json deleted file mode 100644 index 0b225a786de5b..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-histogram/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@superset-ui/legacy-plugin-chart-histogram", - "version": "0.20.3", - "description": "Superset Legacy Chart - Histogram", - "sideEffects": [ - "*.css" - ], - "main": "lib/index.js", - "module": "esm/index.js", - "files": [ - "esm", - "lib" - ], - "repository": { - "type": "git", - "url": "https://github.com/apache/superset.git", - "directory": "superset-frontend/packages/legacy-plugin-chart-histogram" - }, - "keywords": [ - "superset" - ], - "author": "Superset", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/apache/superset/issues" - }, - "homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-histogram#readme", - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@data-ui/histogram": "^0.0.84", - "@data-ui/theme": "^0.0.84", - "@vx/legend": "^0.0.199", - "@vx/responsive": "^0.0.199", - "@vx/scale": "^0.0.199", - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "@superset-ui/chart-controls": "*", - "@superset-ui/core": "*", - "react": "^15 || ^16" - } -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/Histogram.jsx b/superset-frontend/plugins/legacy-plugin-chart-histogram/src/Histogram.jsx deleted file mode 100644 index c14b83c1ca84c..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/Histogram.jsx +++ /dev/null @@ -1,160 +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 react/sort-prop-types */ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; -import { Histogram, BarSeries, XAxis, YAxis } from '@data-ui/histogram'; -import { chartTheme } from '@data-ui/theme'; -import { LegendOrdinal } from '@vx/legend'; -import { scaleOrdinal } from '@vx/scale'; -import { CategoricalColorNamespace, styled, t } from '@superset-ui/core'; -import WithLegend from './WithLegend'; - -const propTypes = { - className: PropTypes.string, - data: PropTypes.arrayOf( - PropTypes.shape({ - key: PropTypes.string, - values: PropTypes.arrayOf(PropTypes.number), - }), - ).isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - colorScheme: PropTypes.string, - normalized: PropTypes.bool, - cumulative: PropTypes.bool, - binCount: PropTypes.number, - opacity: PropTypes.number, - xAxisLabel: PropTypes.string, - yAxisLabel: PropTypes.string, - showLegend: PropTypes.bool, -}; -const defaultProps = { - binCount: 15, - className: '', - colorScheme: '', - normalized: false, - cumulative: false, - opacity: 1, - xAxisLabel: '', - yAxisLabel: '', -}; - -class CustomHistogram extends PureComponent { - render() { - const { - className, - data, - width, - height, - binCount, - colorScheme, - normalized, - cumulative, - opacity, - xAxisLabel, - yAxisLabel, - showLegend, - sliceId, - } = this.props; - const colorFn = CategoricalColorNamespace.getScale(colorScheme); - const keys = data.map(d => d.key); - const colorScale = scaleOrdinal({ - domain: keys, - range: keys.map(x => colorFn(x, sliceId)), - }); - - return ( - - showLegend && ( - - ) - } - renderChart={parent => ( - ( -
- - {datum.bin0} {t('to')} {datum.bin1} - -
- {t('count')} - {datum.count} -
-
- {t('cumulative')} - {datum.cumulative} -
-
- {t('percentile (exclusive)')} - {`${( - (datum.cumulativeDensity - datum.density) * - 100 - ).toPrecision(4)}th`} -
-
- )} - valueAccessor={datum => datum} - theme={chartTheme} - > - {data.map(series => ( - - ))} - - -
- )} - /> - ); - } -} - -CustomHistogram.propTypes = propTypes; -CustomHistogram.defaultProps = defaultProps; - -export default styled(CustomHistogram)` - .superset-legacy-chart-histogram { - overflow: hidden; - } -`; diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/WithLegend.jsx b/superset-frontend/plugins/legacy-plugin-chart-histogram/src/WithLegend.jsx deleted file mode 100644 index f127a0ec7d3fb..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/WithLegend.jsx +++ /dev/null @@ -1,153 +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 react/sort-prop-types, react/jsx-sort-default-props */ -import { Component } from 'react'; -import PropTypes from 'prop-types'; -import { ParentSize } from '@vx/responsive'; - -const propTypes = { - className: PropTypes.string, - width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - renderChart: PropTypes.func.isRequired, - renderLegend: PropTypes.func.isRequired, - position: PropTypes.oneOf(['top', 'left', 'bottom', 'right']), - legendJustifyContent: PropTypes.oneOf(['center', 'flex-start', 'flex-end']), -}; -const defaultProps = { - className: '', - width: 'auto', - height: 'auto', - position: 'top', - legendJustifyContent: undefined, -}; - -const LEGEND_STYLE_BASE = { - display: 'flex', - flexGrow: 0, - flexShrink: 0, - order: -1, - paddingTop: '5px', - fontSize: '0.9em', -}; - -const CHART_STYLE_BASE = { - flexGrow: 1, - flexShrink: 1, - flexBasis: 'auto', - position: 'relative', -}; - -class WithLegend extends Component { - getContainerDirection() { - const { position } = this.props; - switch (position) { - case 'left': - return 'row'; - case 'right': - return 'row-reverse'; - case 'bottom': - return 'column-reverse'; - default: - case 'top': - return 'column'; - } - } - - getLegendJustifyContent() { - const { legendJustifyContent, position } = this.props; - if (legendJustifyContent) { - return legendJustifyContent; - } - switch (position) { - case 'left': - return 'flex-start'; - case 'right': - return 'flex-start'; - case 'bottom': - return 'flex-end'; - default: - case 'top': - return 'flex-end'; - } - } - - render() { - const { className, width, height, position, renderChart, renderLegend } = - this.props; - - const isHorizontal = position === 'left' || position === 'right'; - - const style = { - display: 'flex', - flexDirection: this.getContainerDirection(), - }; - if (width) { - style.width = width; - } - if (height) { - style.height = height; - } - - const chartStyle = { ...CHART_STYLE_BASE }; - if (isHorizontal) { - chartStyle.width = 0; - } else { - chartStyle.height = 0; - } - - const legendDirection = isHorizontal ? 'column' : 'row'; - const legendStyle = { - ...LEGEND_STYLE_BASE, - flexDirection: legendDirection, - justifyContent: this.getLegendJustifyContent(), - }; - const legendContainerStyle = { - flexWrap: 'wrap', - display: 'flex', - flexDirection: legendDirection, - }; - return ( -
-
- {renderLegend({ - // Pass flexDirection for @vx/legend to arrange legend items - direction: legendDirection, - style: legendContainerStyle, - })} -
-
- - {parent => - parent.width > 0 && parent.height > 0 - ? // Only render when necessary - renderChart(parent) - : null - } - -
-
- ); - } -} - -WithLegend.propTypes = propTypes; -WithLegend.defaultProps = defaultProps; - -export default WithLegend; diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/controlPanel.ts b/superset-frontend/plugins/legacy-plugin-chart-histogram/src/controlPanel.ts deleted file mode 100644 index 81ca460fe2c66..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/controlPanel.ts +++ /dev/null @@ -1,152 +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 { t, validateNonEmpty } from '@superset-ui/core'; -import { - columnChoices, - ControlPanelConfig, - ControlPanelState, - formatSelectOptions, - getStandardizedControls, - sharedControls, - ControlState, -} from '@superset-ui/chart-controls'; - -const columnsConfig = { - ...sharedControls.columns, - label: t('Columns'), - description: t('Select the numeric columns to draw the histogram'), - mapStateToProps: (state: ControlPanelState, controlState: ControlState) => ({ - ...(sharedControls.columns.mapStateToProps?.(state, controlState) || {}), - choices: columnChoices(state.datasource), - }), - validators: [validateNonEmpty], -}; - -const config: ControlPanelConfig = { - controlPanelSections: [ - { - label: t('Query'), - expanded: true, - controlSetRows: [ - [ - { - name: 'all_columns_x', - config: columnsConfig, - }, - ], - ['adhoc_filters'], - ['row_limit'], - ['groupby'], - ], - }, - { - label: t('Chart Options'), - expanded: true, - controlSetRows: [ - ['color_scheme'], - [ - { - name: 'link_length', - config: { - type: 'SelectControl', - renderTrigger: true, - freeForm: true, - label: t('No of Bins'), - default: 5, - choices: formatSelectOptions([ - '10', - '25', - '50', - '75', - '100', - '150', - '200', - '250', - ]), - description: t('Select the number of bins for the histogram'), - }, - }, - ], - [ - { - name: 'x_axis_label', - config: { - type: 'TextControl', - label: t('X Axis Label'), - renderTrigger: true, - default: '', - }, - }, - ], - [ - { - name: 'y_axis_label', - config: { - type: 'TextControl', - label: t('Y Axis Label'), - renderTrigger: true, - default: '', - }, - }, - ], - [ - { - name: 'show_legend', - config: { - type: 'CheckboxControl', - label: t('Legend'), - renderTrigger: true, - default: true, - description: t('Whether to display the legend (toggles)'), - }, - }, - ], - [ - { - name: 'normalized', - config: { - type: 'CheckboxControl', - label: t('Normalized'), - renderTrigger: true, - description: t('Whether to normalize the histogram'), - default: false, - }, - }, - ], - [ - { - name: 'cumulative', - config: { - type: 'CheckboxControl', - label: t('Cumulative'), - renderTrigger: true, - description: t('Whether to make the histogram cumulative'), - default: false, - }, - }, - ], - ], - }, - ], - formDataOverrides: formData => ({ - ...formData, - groupby: getStandardizedControls().popAllColumns(), - }), -}; -export default config; diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/example1.jpg b/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/example1.jpg deleted file mode 100644 index 9bfacc4da5899..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/example1.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/example2.jpg b/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/example2.jpg deleted file mode 100644 index b4a55095d1353..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/example2.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/example3.jpg b/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/example3.jpg deleted file mode 100644 index 61006e5e17cf8..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/example3.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/thumbnail.png b/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/thumbnail.png deleted file mode 100644 index 22e5ea4243fb6..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/thumbnail.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/thumbnailLarge.png b/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/thumbnailLarge.png deleted file mode 100644 index 3c63c0eb5291c..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/images/thumbnailLarge.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/index.js b/superset-frontend/plugins/legacy-plugin-chart-histogram/src/index.js deleted file mode 100644 index 5704f58333733..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/index.js +++ /dev/null @@ -1,52 +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 { t, ChartMetadata, ChartPlugin } from '@superset-ui/core'; -import transformProps from './transformProps'; -import example1 from './images/example1.jpg'; -import example2 from './images/example2.jpg'; -import example3 from './images/example3.jpg'; -import thumbnail from './images/thumbnail.png'; -import controlPanel from './controlPanel'; - -const metadata = new ChartMetadata({ - category: t('Distribution'), - description: t( - 'Take your data points, and group them into "bins" to see where the densest areas of information lie', - ), - exampleGallery: [ - { url: example1, caption: t('Population age data') }, - { url: example2 }, - { url: example3 }, - ], - name: t('Histogram (legacy)'), - tags: [t('Comparison'), t('Legacy'), t('Pattern'), t('Range')], - thumbnail, - useLegacyApi: true, -}); - -export default class HistogramChartPlugin extends ChartPlugin { - constructor() { - super({ - loadChart: () => import('./Histogram'), - metadata, - transformProps, - controlPanel, - }); - } -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/transformProps.js b/superset-frontend/plugins/legacy-plugin-chart-histogram/src/transformProps.js deleted file mode 100644 index 1de223240498c..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/transformProps.js +++ /dev/null @@ -1,47 +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. - */ -export default function transformProps(chartProps) { - const { width, height, formData, queriesData } = chartProps; - const { - colorScheme, - linkLength, - normalized, - cumulative, - globalOpacity, - xAxisLabel, - yAxisLabel, - showLegend, - sliceId, - } = formData; - - return { - width, - height, - data: queriesData[0].data, - binCount: parseInt(linkLength, 10), - colorScheme, - normalized, - cumulative, - opacity: globalOpacity, - xAxisLabel, - yAxisLabel, - showLegend, - sliceId, - }; -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/tsconfig.json b/superset-frontend/plugins/legacy-plugin-chart-histogram/tsconfig.json deleted file mode 100644 index b6bfaa2d98446..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-histogram/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "declarationDir": "lib", - "outDir": "lib", - "rootDir": "src" - }, - "exclude": [ - "lib", - "test" - ], - "extends": "../../tsconfig.json", - "include": [ - "src/**/*", - "types/**/*", - "../../types/**/*" - ], - "references": [ - { - "path": "../../packages/superset-ui-chart-controls" - }, - { - "path": "../../packages/superset-ui-core" - } - ] -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/CHANGELOG.md b/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/CHANGELOG.md deleted file mode 100644 index ef201f3e882a3..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/CHANGELOG.md +++ /dev/null @@ -1,39 +0,0 @@ - - -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -# [0.20.0](https://github.com/apache/superset/compare/v2021.41.0...v0.20.0) (2024-09-09) - -### Features - -- improve color consistency (save all labels) ([#19038](https://github.com/apache/superset/issues/19038)) ([dc57508](https://github.com/apache/superset/commit/dc575080d7e43d40b1734bb8f44fdc291cb95b11)) - -# [0.19.0](https://github.com/apache/superset/compare/v2021.41.0...v0.19.0) (2024-09-07) - -### Features - -- improve color consistency (save all labels) ([#19038](https://github.com/apache/superset/issues/19038)) ([dc57508](https://github.com/apache/superset/commit/dc575080d7e43d40b1734bb8f44fdc291cb95b11)) - -# [0.18.0](https://github.com/apache-superset/superset-ui/compare/v0.17.87...v0.18.0) (2021-08-30) - -**Note:** Version bump only for package @superset-ui/legacy-plugin-chart-sankey-loop diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/README.md b/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/README.md deleted file mode 100644 index 631b00d62a634..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/README.md +++ /dev/null @@ -1,52 +0,0 @@ - - -## @superset-ui/legacy-plugin-chart-sankey-loop - -[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-sankey.svg?style=flat)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-sankey) -[![Libraries.io](https://img.shields.io/librariesio/release/npm/%40superset-ui%2Flegacy-plugin-chart-sankey-loop?style=flat)](https://libraries.io/npm/@superset-ui%2Flegacy-plugin-chart-sankey-loop) - -This plugin provides Sankey Diagram with loops for Superset. - -### Usage - -Configure `key`, which can be any `string`, and register the plugin. This `key` will be used to -lookup this chart throughout the app. - -```js -import SankeyLoopChartPlugin from '@superset-ui/legacy-plugin-chart-sankey-loop'; - -new SankeyLoopChartPlugin().configure({ key: 'sankey' }).register(); -``` - -Then use it via `SuperChart`. See -[storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-sankey-loop) -for more details. - -```js - -``` diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/package.json b/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/package.json deleted file mode 100644 index 6ff25abf3cee0..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@superset-ui/legacy-plugin-chart-sankey-loop", - "version": "0.20.3", - "description": "Superset Legacy Chart - Sankey Diagram with Loops", - "keywords": [ - "superset" - ], - "homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-sankey-loop#readme", - "bugs": { - "url": "https://github.com/apache/superset/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/apache/superset.git", - "directory": "superset-frontend/plugins/legacy-plugin-chart-sankey-loop" - }, - "license": "Apache-2.0", - "author": "Superset", - "main": "lib/index.js", - "module": "esm/index.js", - "files": [ - "esm", - "lib" - ], - "dependencies": { - "d3-sankey-diagram": "^0.7.3", - "d3-selection": "^3.0.0", - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "@superset-ui/chart-controls": "*", - "@superset-ui/core": "*", - "react": "^16.13.1" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/ReactSankeyLoop.jsx b/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/ReactSankeyLoop.jsx deleted file mode 100644 index 5f1e551b0337a..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/ReactSankeyLoop.jsx +++ /dev/null @@ -1,71 +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 { reactify, styled } from '@superset-ui/core'; -import Component from './SankeyLoop'; - -const ReactComponent = reactify(Component); - -const Sankey = ({ className, ...otherProps }) => ( -
- -
-); - -export default styled(Sankey)` - ${({ theme }) => ` - .superset-legacy-chart-sankey-loop .node rect { - cursor: move; - fill-opacity: ${theme.opacity.heavy}; - shape-rendering: crispEdges; - } - - .superset-legacy-chart-sankey-loop .node text { - pointer-events: none; - text-shadow: 0 1px 0 ${theme.colors.grayscale.light5}; - } - - .superset-legacy-chart-sankey-loop .link { - fill: none; - stroke: ${theme.colors.grayscale.dark2}; - stroke-opacity: ${theme.opacity.light}; - } - - .superset-legacy-chart-sankey-loop .link:hover { - stroke-opacity: ${theme.opacity.mediumHeavy}; - } - - .superset-legacy-chart-sankey-loop .link path { - opacity: ${theme.opacity.mediumLight}; - stroke-opacity: 0; - } - - .superset-legacy-chart-sankey-loop .link:hover path { - opacity: ${theme.opacity.heavy}; - } - - .superset-legacy-chart-sankey-loop .link text { - fill: ${theme.colors.grayscale.base}; - font-size: ${theme.gridUnit * 3}px; - } - - .superset-legacy-chart-sankey-loop .link:hover text { - opacity: 1; - } - `} -`; diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/SankeyLoop.js b/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/SankeyLoop.js deleted file mode 100644 index c9fe27eb23510..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/SankeyLoop.js +++ /dev/null @@ -1,140 +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 react/sort-prop-types */ -import PropTypes from 'prop-types'; -import { select } from 'd3-selection'; -import { sankeyDiagram, sankey } from 'd3-sankey-diagram'; -import { - getNumberFormatter, - NumberFormats, - CategoricalColorNamespace, -} from '@superset-ui/core'; - -// a problem with 'd3-sankey-diagram' is that the sankey().extent() parameters, which -// informs the layout of the bounding box of the sankey columns, does not account -// for labels and paths which happen to be layed out outside that rectangle. -// for that reason i've selected relatively large default left/right margins, and have -// made 'margin' a property. i have raised an issue in the chart repo: -// -// https://github.com/ricklupton/d3-sankey-diagram/issues/20 - -const defaultMargin = { - top: 0, - right: 80, - bottom: 0, - left: 80, -}; - -const propTypes = { - data: PropTypes.arrayOf( - PropTypes.shape({ - source: PropTypes.string, - target: PropTypes.string, - value: PropTypes.number, - }), - ), - width: PropTypes.number, - height: PropTypes.number, - colorScheme: PropTypes.string, - margin: PropTypes.shape({ - top: PropTypes.number, - right: PropTypes.number, - bottom: PropTypes.number, - left: PropTypes.number, - }), -}; - -const percentFormat = getNumberFormatter(NumberFormats.PERCENT_1_POINT); -const countFormat = getNumberFormatter(); - -function computeGraph(links) { - // this assumes source and target are string values - const nodes = Array.from( - links.reduce( - (set, { source, target }) => set.add(source).add(target), - new Set(), - ), - ).map(id => ({ id, name: id })); - - return { - nodes, - - // links are shallow copied as the chart layout modifies them, and it is best to - // leave the passed data un-altered - links: links.map(d => ({ ...d })), - }; -} - -function SankeyLoop(element, props) { - const { data, width, height, colorScheme, sliceId } = props; - const colorFn = CategoricalColorNamespace.getScale(colorScheme); - const margin = { ...defaultMargin, ...props.margin }; - const innerWidth = width - margin.left - margin.right; - const innerHeight = height - margin.top - margin.bottom; - - const layout = sankey() - .nodeId(d => d.id) - .extent([ - [margin.left, margin.top], - [innerWidth, innerHeight], - ]); - - const diagram = sankeyDiagram() - .nodeTitle(d => d.name) - .linkTitle( - ({ - source: { name: sName, value: sValue }, - target: { name: tName }, - value, - }) => - `${sName} → ${tName}: ${countFormat(value)} (${percentFormat( - value / sValue, - )})`, - ) - .linkColor(d => colorFn(d.source.name, sliceId)); - - const div = select(element); - div.selectAll('*').remove(); - - const svg = div - .append('svg') - .classed('superset-legacy-chart-sankey-loop', true) - .style('width', width) - .style('height', height) - .datum(layout(computeGraph(data))) - .call(diagram); - - svg - .selectAll('g.link') - .classed('link', true) - .append('text') - .attr('x', d => d.points[0].x) - .attr('y', d => d.points[0].y) - .attr('dy', 3) - .attr('dx', 2) - .text( - d => - `${countFormat(d.value)} (${percentFormat(d.value / d.source.value)})`, - ); -} - -SankeyLoop.displayName = 'SankeyLoop'; -SankeyLoop.propTypes = propTypes; - -export default SankeyLoop; diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/images/thumbnail.png b/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/images/thumbnail.png deleted file mode 100644 index 5d814582a51d0..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/images/thumbnail.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/images/thumbnailLarge.png b/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/images/thumbnailLarge.png deleted file mode 100644 index 5d814582a51d0..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/images/thumbnailLarge.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/index.js b/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/index.js deleted file mode 100644 index 38922d5a778e4..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/index.js +++ /dev/null @@ -1,41 +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 { t, ChartMetadata, ChartPlugin } from '@superset-ui/core'; -import transformProps from './transformProps'; -import thumbnail from './images/thumbnail.png'; -import controlPanel from './controlPanel'; - -const metadata = new ChartMetadata({ - credits: ['https://github.com/ricklupton/d3-sankey-diagram'], - description: '', - name: t('Sankey Diagram with Loops'), - thumbnail, - useLegacyApi: true, -}); - -export default class SankeyChartPlugin extends ChartPlugin { - constructor() { - super({ - loadChart: () => import('./ReactSankeyLoop'), - metadata, - transformProps, - controlPanel, - }); - } -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/transformProps.js b/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/transformProps.js deleted file mode 100644 index 76c0c220a7681..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/transformProps.js +++ /dev/null @@ -1,31 +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. - */ -export default function transformProps(chartProps) { - const { width, height, formData, queriesData, margin } = chartProps; - const { colorScheme, sliceId } = formData; - - return { - width, - height, - data: queriesData[0].data, - colorScheme, - margin, - sliceId, - }; -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/tsconfig.json b/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/tsconfig.json deleted file mode 100644 index b6bfaa2d98446..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "declarationDir": "lib", - "outDir": "lib", - "rootDir": "src" - }, - "exclude": [ - "lib", - "test" - ], - "extends": "../../tsconfig.json", - "include": [ - "src/**/*", - "types/**/*", - "../../types/**/*" - ], - "references": [ - { - "path": "../../packages/superset-ui-chart-controls" - }, - { - "path": "../../packages/superset-ui-core" - } - ] -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/CHANGELOG.md b/superset-frontend/plugins/legacy-plugin-chart-sankey/CHANGELOG.md deleted file mode 100644 index c9fdad1a6b262..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey/CHANGELOG.md +++ /dev/null @@ -1,55 +0,0 @@ - - -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -# [0.20.0](https://github.com/apache/superset/compare/v2021.41.0...v0.20.0) (2024-09-09) - -### Bug Fixes - -- **Dashboard:** Color inconsistency on refreshes and conflicts ([#27439](https://github.com/apache/superset/issues/27439)) ([313ee59](https://github.com/apache/superset/commit/313ee596f5435894f857d72be7269d5070c8c964)) - -### Features - -- Adds the ECharts Sankey chart ([#29329](https://github.com/apache/superset/issues/29329)) ([c83d5b8](https://github.com/apache/superset/commit/c83d5b88e159413d09fb346a95201255b1b5e196)) -- apply standardized form data to tier 2 charts ([#20530](https://github.com/apache/superset/issues/20530)) ([de524bc](https://github.com/apache/superset/commit/de524bc59f011fd361dcdb7d35c2cb51f7eba442)) -- improve color consistency (save all labels) ([#19038](https://github.com/apache/superset/issues/19038)) ([dc57508](https://github.com/apache/superset/commit/dc575080d7e43d40b1734bb8f44fdc291cb95b11)) - -# [0.19.0](https://github.com/apache/superset/compare/v2021.41.0...v0.19.0) (2024-09-07) - -### Bug Fixes - -- **Dashboard:** Color inconsistency on refreshes and conflicts ([#27439](https://github.com/apache/superset/issues/27439)) ([313ee59](https://github.com/apache/superset/commit/313ee596f5435894f857d72be7269d5070c8c964)) - -### Features - -- Adds the ECharts Sankey chart ([#29329](https://github.com/apache/superset/issues/29329)) ([c83d5b8](https://github.com/apache/superset/commit/c83d5b88e159413d09fb346a95201255b1b5e196)) -- apply standardized form data to tier 2 charts ([#20530](https://github.com/apache/superset/issues/20530)) ([de524bc](https://github.com/apache/superset/commit/de524bc59f011fd361dcdb7d35c2cb51f7eba442)) -- improve color consistency (save all labels) ([#19038](https://github.com/apache/superset/issues/19038)) ([dc57508](https://github.com/apache/superset/commit/dc575080d7e43d40b1734bb8f44fdc291cb95b11)) - -# [0.18.0](https://github.com/apache-superset/superset-ui/compare/v0.17.87...v0.18.0) (2021-08-30) - -**Note:** Version bump only for package @superset-ui/legacy-plugin-chart-sankey - -## [0.17.61](https://github.com/apache-superset/superset-ui/compare/v0.17.60...v0.17.61) (2021-07-02) - -**Note:** Version bump only for package @superset-ui/legacy-plugin-chart-sankey diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/README.md b/superset-frontend/plugins/legacy-plugin-chart-sankey/README.md deleted file mode 100644 index 675908523f6b6..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey/README.md +++ /dev/null @@ -1,52 +0,0 @@ - - -## @superset-ui/legacy-plugin-chart-sankey - -[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-sankey.svg?style=flat)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-sankey) -[![Libraries.io](https://img.shields.io/librariesio/release/npm/%40superset-ui%2Flegacy-plugin-chart-sankey?style=flat)](https://libraries.io/npm/@superset-ui%2Flegacy-plugin-chart-sankey) - -This plugin provides Sankey Diagram for Superset. - -### Usage - -Configure `key`, which can be any `string`, and register the plugin. This `key` will be used to -lookup this chart throughout the app. - -```js -import SankeyChartPlugin from '@superset-ui/legacy-plugin-chart-sankey'; - -new SankeyChartPlugin().configure({ key: 'sankey' }).register(); -``` - -Then use it via `SuperChart`. See -[storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-sankey) -for more details. - -```js - -``` diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/package.json b/superset-frontend/plugins/legacy-plugin-chart-sankey/package.json deleted file mode 100644 index 5aceb132af168..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "@superset-ui/legacy-plugin-chart-sankey", - "version": "0.20.3", - "description": "Superset Legacy Chart - Sankey Diagram", - "sideEffects": [ - "*.css" - ], - "main": "lib/index.js", - "module": "esm/index.js", - "files": [ - "esm", - "lib" - ], - "repository": { - "type": "git", - "url": "https://github.com/apache/superset.git", - "directory": "superset-frontend/packages/legacy-plugin-chart-sankey" - }, - "keywords": [ - "superset" - ], - "author": "Superset", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/apache/superset/issues" - }, - "homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-sankey#readme", - "publishConfig": { - "access": "public" - }, - "dependencies": { - "d3": "^3.5.17", - "d3-sankey": "^0.4.2", - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "@superset-ui/chart-controls": "*", - "@superset-ui/core": "*", - "react": "^16.13.1" - } -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/ReactSankey.jsx b/superset-frontend/plugins/legacy-plugin-chart-sankey/src/ReactSankey.jsx deleted file mode 100644 index 48efa7b896c10..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/ReactSankey.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 { reactify, styled } from '@superset-ui/core'; -import PropTypes from 'prop-types'; -import SanKey from './Sankey'; - -const ReactSanKey = reactify(SanKey); - -const SankeyComponent = ({ className, ...otherProps }) => ( -
- -
-); - -SankeyComponent.propTypes = { - className: PropTypes.string.isRequired, -}; - -export default styled(SankeyComponent)` - ${({ theme }) => ` - .superset-legacy-chart-sankey { - .node { - rect { - cursor: move; - fill-opacity: ${theme.opacity.heavy}; - shape-rendering: crispEdges; - } - text { - pointer-events: none; - text-shadow: 0 1px 0 ${theme.colors.grayscale.light5}; - font-size: ${theme.typography.sizes.s}px; - } - } - .link { - fill: none; - stroke: ${theme.colors.grayscale.dark2}; - stroke-opacity: ${theme.opacity.light}; - &:hover { - stroke-opacity: ${theme.opacity.mediumLight}; - } - } - .opacity-0 { - opacity: 0; - } - } - .sankey-tooltip { - position: absolute; - width: auto; - background: ${theme.colors.grayscale.light2}; - padding: ${theme.gridUnit * 3}px; - font-size: ${theme.typography.sizes.s}px; - color: ${theme.colors.grayscale.dark2}; - border: 1px solid ${theme.colors.grayscale.light5}; - text-align: center; - pointer-events: none; - } - `} -`; diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/Sankey.js b/superset-frontend/plugins/legacy-plugin-chart-sankey/src/Sankey.js deleted file mode 100644 index a38142c564680..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/Sankey.js +++ /dev/null @@ -1,248 +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-param-reassign */ -/* eslint-disable react/sort-prop-types */ -import d3 from 'd3'; -import PropTypes from 'prop-types'; -import { sankey as d3Sankey } from 'd3-sankey'; -import { - getNumberFormatter, - NumberFormats, - CategoricalColorNamespace, -} from '@superset-ui/core'; -import { getOverlappingElements } from './utils'; - -const propTypes = { - data: PropTypes.arrayOf( - PropTypes.shape({ - source: PropTypes.string, - target: PropTypes.string, - value: PropTypes.number, - }), - ), - width: PropTypes.number, - height: PropTypes.number, - colorScheme: PropTypes.string, -}; - -const formatNumber = getNumberFormatter(NumberFormats.FLOAT); - -function Sankey(element, props) { - const { data, width, height, colorScheme, sliceId } = props; - const div = d3.select(element); - div.classed(`superset-legacy-chart-sankey`, true); - const margin = { - top: 5, - right: 5, - bottom: 5, - left: 5, - }; - const innerWidth = width - margin.left - margin.right; - const innerHeight = height - margin.top - margin.bottom; - - div.selectAll('*').remove(); - const tooltip = div - .append('div') - .attr('class', 'sankey-tooltip') - .style('opacity', 0); - const svg = div - .append('svg') - .attr('width', innerWidth + margin.left + margin.right) - .attr('height', innerHeight + margin.top + margin.bottom) - .append('g') - .attr('transform', `translate(${margin.left},${margin.top})`); - const colorFn = CategoricalColorNamespace.getScale(colorScheme); - - const sankey = d3Sankey() - .nodeWidth(15) - .nodePadding(10) - .size([innerWidth, innerHeight]); - - const path = sankey.link(); - - let nodes = {}; - // Compute the distinct nodes from the links. - const links = data.map(row => { - const link = { ...row }; - link.source = - nodes[link.source] || (nodes[link.source] = { name: link.source }); - link.target = - nodes[link.target] || (nodes[link.target] = { name: link.target }); - link.value = Number(link.value); - - return link; - }); - nodes = d3.values(nodes); - - sankey.nodes(nodes).links(links).layout(32); - - function getTooltipHtml(d) { - let html; - - if (d.sourceLinks) { - // is node - html = `${d.name} Value: ${formatNumber( - d.value, - )}`; - } else { - const val = formatNumber(d.value); - const sourcePercent = d3.round((d.value / d.source.value) * 100, 1); - const targetPercent = d3.round((d.value / d.target.value) * 100, 1); - - html = [ - "
Path Value: ", - val, - '
', - "
", - "", - Number.isFinite(sourcePercent) ? sourcePercent : '100', - '% of ', - d.source.name, - '
', - `${ - Number.isFinite(targetPercent) ? targetPercent : '--' - }% of `, - d.target.name, - '
', - ].join(''); - } - - return html; - } - - function onmouseover(d) { - tooltip - .html(() => getTooltipHtml(d)) - .transition() - .duration(200); - const { height: tooltipHeight, width: tooltipWidth } = tooltip - .node() - .getBoundingClientRect(); - tooltip - .style( - 'left', - `${Math.min(d3.event.offsetX + 10, width - tooltipWidth)}px`, - ) - .style( - 'top', - `${Math.min(d3.event.offsetY + 10, height - tooltipHeight)}px`, - ) - .style('position', 'absolute') - .style('opacity', 0.95); - } - - function onmouseout() { - tooltip.transition().duration(100).style('opacity', 0); - } - - const link = svg - .append('g') - .selectAll('.link') - .data(links) - .enter() - .append('path') - .attr('class', 'link') - .attr('d', path) - .style('stroke-width', d => Math.max(1, d.dy)) - .sort((a, b) => b.dy - a.dy) - .on('mouseover', onmouseover) - .on('mouseout', onmouseout); - - function dragmove(d) { - d3.select(this).attr( - 'transform', - `translate(${d.x},${(d.y = Math.max( - 0, - Math.min(height - d.dy, d3.event.y), - ))})`, - ); - sankey.relayout(); - link.attr('d', path); - } - - function checkVisibility() { - const elements = div.selectAll('.node')[0] ?? []; - const overlappingElements = getOverlappingElements(elements); - - elements.forEach(el => { - const text = el.getElementsByTagName('text')[0]; - - if (text) { - if (overlappingElements.includes(el)) { - text.classList.add('opacity-0'); - } else { - text.classList.remove('opacity-0'); - } - } - }); - } - - const node = svg - .append('g') - .selectAll('.node') - .data(nodes) - .enter() - .append('g') - .attr('class', 'node') - .attr('transform', d => `translate(${d.x},${d.y})`) - .call( - d3.behavior - .drag() - .origin(d => d) - .on('dragstart', function dragStart() { - this.parentNode.append(this); - }) - .on('drag', dragmove) - .on('dragend', checkVisibility), - ); - const minRectHeight = 5; - node - .append('rect') - .attr('height', d => (d.dy > minRectHeight ? d.dy : minRectHeight)) - .attr('width', sankey.nodeWidth()) - .style('fill', d => { - const name = d.name || 'N/A'; - d.color = colorFn(name, sliceId); - - return d.color; - }) - .style('stroke', d => d3.rgb(d.color).darker(2)) - .on('mouseover', onmouseover) - .on('mouseout', onmouseout); - - node - .append('text') - .attr('x', -6) - .attr('y', d => d.dy / 2) - .attr('dy', '.35em') - .attr('text-anchor', 'end') - .attr('transform', null) - .text(d => d.name) - .attr('class', 'opacity-0') - .filter(d => d.x < innerWidth / 2) - .attr('x', 6 + sankey.nodeWidth()) - .attr('text-anchor', 'start'); - - checkVisibility(); -} - -Sankey.displayName = 'Sankey'; -Sankey.propTypes = propTypes; - -export default Sankey; diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/controlPanel.ts b/superset-frontend/plugins/legacy-plugin-chart-sankey/src/controlPanel.ts deleted file mode 100644 index 7505f1a4a0ac5..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/controlPanel.ts +++ /dev/null @@ -1,68 +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 { t } from '@superset-ui/core'; -import { - ControlPanelConfig, - getStandardizedControls, -} from '@superset-ui/chart-controls'; - -const config: ControlPanelConfig = { - controlPanelSections: [ - { - label: t('Query'), - expanded: true, - controlSetRows: [ - [ - { - name: 'groupby', - override: { - label: t('Source / Target'), - description: t('Choose a source and a target'), - }, - }, - ], - ['metric'], - ['adhoc_filters'], - [ - { - name: 'row_limit', - override: { - description: t( - 'Limiting rows may result in incomplete data and misleading charts. Consider filtering or grouping source/target names instead.', - ), - }, - }, - ], - ['sort_by_metric'], - ], - }, - { - label: t('Chart Options'), - expanded: true, - controlSetRows: [['color_scheme']], - }, - ], - formDataOverrides: formData => ({ - ...formData, - groupby: getStandardizedControls().popAllColumns(), - metric: getStandardizedControls().shiftMetric(), - }), -}; - -export default config; diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/images/Sankey.jpg b/superset-frontend/plugins/legacy-plugin-chart-sankey/src/images/Sankey.jpg deleted file mode 100644 index 06b1fa8a4e88f..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/images/Sankey.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/images/Sankey2.jpg b/superset-frontend/plugins/legacy-plugin-chart-sankey/src/images/Sankey2.jpg deleted file mode 100644 index 15bca65a19d52..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/images/Sankey2.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/images/thumbnail.png b/superset-frontend/plugins/legacy-plugin-chart-sankey/src/images/thumbnail.png deleted file mode 100644 index 6bf13f8fd623c..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/images/thumbnail.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/images/thumbnailLarge.png b/superset-frontend/plugins/legacy-plugin-chart-sankey/src/images/thumbnailLarge.png deleted file mode 100644 index 231316b88af63..0000000000000 Binary files a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/images/thumbnailLarge.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/index.js b/superset-frontend/plugins/legacy-plugin-chart-sankey/src/index.js deleted file mode 100644 index e679441187a10..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/index.js +++ /dev/null @@ -1,58 +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 { t, ChartMetadata, ChartPlugin } from '@superset-ui/core'; -import transformProps from './transformProps'; -import thumbnail from './images/thumbnail.png'; -import example1 from './images/Sankey.jpg'; -import example2 from './images/Sankey2.jpg'; -import controlPanel from './controlPanel'; - -const metadata = new ChartMetadata({ - category: t('Flow'), - credits: ['https://github.com/d3/d3-sankey'], - description: t( - "Visualizes the flow of different group's values through different stages of a system. New stages in the pipeline are visualized as nodes or layers. The thickness of the bars or edges represent the metric being visualized.", - ), - exampleGallery: [ - { url: example1, description: t('Demographics') }, - { url: example2, description: t('Survey Responses') }, - ], - name: t('Sankey Diagram (legacy)'), - tags: [ - t('Categorical'), - t('Directional'), - t('Legacy'), - t('Percentages'), - t('Proportional'), - t('Relational'), - ], - thumbnail, - useLegacyApi: true, -}); - -export default class SankeyChartPlugin extends ChartPlugin { - constructor() { - super({ - loadChart: () => import('./ReactSankey'), - metadata, - transformProps, - controlPanel, - }); - } -} diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/tests/utils.test.js b/superset-frontend/plugins/legacy-plugin-chart-sankey/src/tests/utils.test.js deleted file mode 100644 index d679d6bbc6c8d..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/tests/utils.test.js +++ /dev/null @@ -1,93 +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 { getOverlappingElements, isOverlapping } from '../utils'; - -const overlapRects = [ - { - x: 10, - y: 10, - width: 10, - height: 10, - }, - { - x: 12, - y: 12, - width: 12, - height: 12, - }, - { - x: 32, - y: 32, - width: 32, - height: 32, - }, -]; - -const notOverlapRects = [ - { - x: 10, - y: 10, - width: 10, - height: 10, - }, - { - x: 24, - y: 15, - width: 15, - height: 15, - }, - { - x: 32, - y: 32, - width: 32, - height: 32, - }, -]; - -const createSVGs = objects => - objects.map(data => { - const el = document.createElementNS('http://www.w3.org/2000/svg', 'text'); - el.getBoundingClientRect = jest.fn(() => data); - - return el; - }); - -// https://www.khanacademy.org/computer-programming/rectx-y-width-height-radius/839496660 -describe('legacy-plugin-chart-sankey/utils', () => { - it('isOverlapping to be truthy', () => { - const [rect1, rect2] = overlapRects; - expect(isOverlapping(rect1, rect2)).toBeTruthy(); - }); - - it('isOverlapping to be falsy', () => { - const [rect1, rect2] = notOverlapRects; - expect(isOverlapping(rect1, rect2)).toBeFalsy(); - }); - - it('getOverlappingElements to be truthy', () => { - const elements = createSVGs(overlapRects); - expect(getOverlappingElements(elements).length).toBe(2); - }); - - it('getOverlappingElements to be falsy', () => { - const elements = createSVGs(notOverlapRects); - expect(getOverlappingElements(elements).length).toBe(0); - }); -}); diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/utils.ts b/superset-frontend/plugins/legacy-plugin-chart-sankey/src/utils.ts deleted file mode 100644 index a68dd7bbfbc8c..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/utils.ts +++ /dev/null @@ -1,81 +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. - */ -type Rect = { - x: number; - y: number; - width: number; - height: number; -}; - -export function getLabelFontSize(width: number): number { - if (width > 550) { - return 0.8; - } - - if (width > 400 && width <= 550) { - return 0.55; - } - - return 0.45; -} - -export const isOverlapping = (rect1: Rect, rect2: Rect): boolean => { - const { x: x1, y: y1, width: width1, height: height1 } = rect1; - const { x: x2, y: y2, width: width2, height: height2 } = rect2; - - return !( - x1 > x2 + width2 || - x1 + width1 < x2 || - y1 > y2 + height2 || - y1 + height1 < y2 - ); -}; - -export const getRectangle = (element: SVGElement, offset = 0): Rect => { - const { x, y, width, height } = element.getBoundingClientRect(); - - return { - x, - y: y + offset, - width, - height: height - offset * 2, - }; -}; - -export const getOverlappingElements = ( - elements: SVGElement[], -): SVGElement[] => { - const overlappingElements: SVGElement[] = []; - - elements.forEach((e1, index1) => { - const rect1: Rect = getRectangle(e1, 1); - - elements.forEach((e2, index2) => { - if (index2 <= index1) return; - const rect2: Rect = getRectangle(e2, 1); - - if (isOverlapping(rect1, rect2)) { - overlappingElements.push(elements[index2]); - overlappingElements.push(elements[index1]); - } - }); - }); - - return overlappingElements; -}; diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/tsconfig.json b/superset-frontend/plugins/legacy-plugin-chart-sankey/tsconfig.json deleted file mode 100644 index b6bfaa2d98446..0000000000000 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "declarationDir": "lib", - "outDir": "lib", - "rootDir": "src" - }, - "exclude": [ - "lib", - "test" - ], - "extends": "../../tsconfig.json", - "include": [ - "src/**/*", - "types/**/*", - "../../types/**/*" - ], - "references": [ - { - "path": "../../packages/superset-ui-chart-controls" - }, - { - "path": "../../packages/superset-ui-core" - } - ] -} diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.test.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.test.ts new file mode 100644 index 0000000000000..87b134f8bc4ef --- /dev/null +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.test.ts @@ -0,0 +1,145 @@ +/** + * 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 { JsonObject, QueryFormData } from '@superset-ui/core'; +import { getAggFunc, commonLayerProps } from './common'; + +const partialformData: Partial = { + viz_type: 'table', + datasource: '3_sqla', +}; + +describe('getAggFunc', () => { + it('returns correct function for sum', () => { + const aggFunc = getAggFunc('sum'); + const result = aggFunc([1, 2, 3, 4]); + expect(result).toBe(10); + }); + + it('returns correct function for min', () => { + const aggFunc = getAggFunc('min'); + const result = aggFunc([1, 2, 3, 4]); + expect(result).toBe(1); + }); + + it('returns correct function for max', () => { + const aggFunc = getAggFunc('max'); + const result = aggFunc([1, 2, 3, 4]); + expect(result).toBe(4); + }); + + it('returns correct function for mean', () => { + const aggFunc = getAggFunc('mean'); + const result = aggFunc([1, 2, 3, 4]); + expect(result).toBe(2.5); + }); + + it('returns correct function for median', () => { + const aggFunc = getAggFunc('median'); + const result = aggFunc([1, 2, 3, 4, 5]); + expect(result).toBe(3); + }); + + it('returns correct function for variance', () => { + const aggFunc = getAggFunc('variance'); + const result = aggFunc([1, 2, 3, 4]); + expect(result).toBeCloseTo(1.6666666666666667); + }); + + it('returns correct function for deviation', () => { + const aggFunc = getAggFunc('deviation'); + const result = aggFunc([1, 2, 3, 4]); + expect(result).toBeCloseTo(1.29099, 5); + }); + + it('returns correct function for count', () => { + const aggFunc = getAggFunc('count'); + const result = aggFunc([1, 2, 3, 4]); + expect(result).toBe(4); + }); + + it('returns correct function for p95 (percentiles)', () => { + const aggFunc = getAggFunc('p95'); + const result = aggFunc([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + expect(result).toBeCloseTo(9.55, 5); + }); + + it('throws an error for unsupported aggregation type', () => { + expect(() => getAggFunc('unsupported')).toThrow( + 'Unsupported aggregation type: unsupported', + ); + }); +}); + +describe('commonLayerProps', () => { + const mockSetTooltip = jest.fn(); + const mockSetTooltipContent = jest.fn( + () => (o: JsonObject) => `Tooltip for ${o}`, + ); + const mockOnSelect = jest.fn(); + + it('returns correct props when js_tooltip is provided', () => { + const formData = { + ...partialformData, + js_tooltip: 'tooltip => tooltip.content', + } as QueryFormData; + const props = commonLayerProps( + formData, + mockSetTooltip, + mockSetTooltipContent, + ); + expect(props.pickable).toBe(true); + expect(props.onHover).toBeDefined(); + }); + + it('calls onHover and sets tooltip', () => { + const formData = { ...partialformData, js_tooltip: null } as QueryFormData; + const props = commonLayerProps( + formData, + mockSetTooltip, + mockSetTooltipContent, + ); + + const mockObject = { picked: true, x: 10, y: 20 }; + props.onHover?.(mockObject); + expect(mockSetTooltip).toHaveBeenCalledWith({ + content: expect.any(Function), // Matches any function + x: 10, + y: 20, + }); + }); + + it('calls onSelect when table_filter is enabled', () => { + const formData = { + ...partialformData, + table_filter: true, + line_column: 'name', + } as QueryFormData; + const props = commonLayerProps( + formData, + mockSetTooltip, + mockSetTooltipContent, + mockOnSelect, + ); + + const mockObject = { object: { name: 'John Doe' } }; + props.onClick?.(mockObject); + expect(mockOnSelect).toHaveBeenCalledWith('John Doe'); + }); +}); diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.tsx index 80f0371e5cfb7..4185b38167807 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.tsx @@ -11,13 +11,23 @@ * * 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 + * "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 { ReactNode } from 'react'; -import * as d3array from 'd3-array'; +import { + ascending as d3ascending, + quantile as d3quantile, + sum as d3sum, + mean as d3mean, + min as d3min, + max as d3max, + median as d3median, + variance as d3variance, + deviation as d3deviation, +} from 'd3-array'; import { JsonObject, JsonValue, QueryFormData } from '@superset-ui/core'; import sandboxedEval from '../utils/sandbox'; import { TooltipProps } from '../components/Tooltip'; @@ -76,6 +86,17 @@ const percentiles = { p99: 0.99, }; +/* Supported d3-array functions */ +const d3functions: Record = { + sum: d3sum, + min: d3min, + max: d3max, + mean: d3mean, + median: d3median, + variance: d3variance, + deviation: d3deviation, +}; + /* Get a stat function that operates on arrays, aligns with control=js_agg_function */ export function getAggFunc( type = 'sum', @@ -84,29 +105,34 @@ export function getAggFunc( if (type === 'count') { return (arr: number[]) => arr.length; } + let d3func: ( iterable: Array, accessor?: (object: JsonObject) => number | undefined, ) => number[] | number | undefined; + if (type in percentiles) { d3func = (arr, acc: (object: JsonObject) => number | undefined) => { let sortedArr; if (accessor) { sortedArr = arr.sort((o1: JsonObject, o2: JsonObject) => - d3array.ascending(accessor(o1), accessor(o2)), + d3ascending(accessor(o1), accessor(o2)), ); } else { - sortedArr = arr.sort(d3array.ascending); + sortedArr = arr.sort(d3ascending); } - return d3array.quantile(sortedArr, percentiles[type], acc); + return d3quantile(sortedArr, percentiles[type], acc); }; + } else if (type in d3functions) { + d3func = d3functions[type]; } else { - d3func = d3array[type]; + throw new Error(`Unsupported aggregation type: ${type}`); } + if (!accessor) { - return (arr: JsonObject[]) => d3func(arr); + return (arr: number[]) => d3func(arr); } - return (arr: JsonObject[]) => d3func(arr.map(x => accessor(x))); + return (arr: number[]) => d3func(arr.map(x => accessor(x))); } diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts index aac2a739e69f6..55d05fe5b6bdf 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts @@ -25,7 +25,6 @@ import { QueryFormData, SequentialScheme, } from '@superset-ui/core'; -import { isNumber } from 'lodash'; import { hexToRGB } from './utils/colors'; const DEFAULT_NUM_BUCKETS = 10; @@ -140,7 +139,7 @@ export function getBreakPointColorScaler( } else { // interpolate colors linearly const linearScaleDomain = extent(features, accessor); - if (!linearScaleDomain.some(isNumber)) { + if (!linearScaleDomain.some(i => typeof i === 'number')) { scaler = colorScheme.createLinearScale(); } else { scaler = colorScheme.createLinearScale( diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils/sandbox.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils/sandbox.ts index bf41f1f200d0e..f9accf445dec9 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils/sandbox.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils/sandbox.ts @@ -19,7 +19,9 @@ // A safe alternative to JS's eval import vm, { Context, RunningScriptOptions } from 'vm'; import _ from 'underscore'; +/* eslint-disable-next-line no-restricted-syntax */ import * as d3array from 'd3-array'; +/* eslint-disable-next-line no-restricted-syntax */ import * as colors from './colors'; // Objects exposed here should be treated like a public API diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/controlPanel.ts deleted file mode 100644 index d06883a222f08..0000000000000 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/controlPanel.ts +++ /dev/null @@ -1,92 +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 { t } from '@superset-ui/core'; -import { ControlPanelConfig, sections } from '@superset-ui/chart-controls'; -import { - lineInterpolation, - showBrush, - showLegend, - showControls, - xAxisLabel, - bottomMargin, - xTicksLayout, - xAxisFormat, - yLogScale, - yAxisBounds, - xAxisShowMinmax, - richTooltip, - timeSeriesSection, -} from '../NVD3Controls'; - -const config: ControlPanelConfig = { - controlPanelSections: [ - sections.legacyTimeseriesTime, - timeSeriesSection[0], - { - label: t('Chart Options'), - expanded: true, - controlSetRows: [ - [showBrush, showLegend], - [ - lineInterpolation, - { - name: 'stacked_style', - config: { - type: 'SelectControl', - label: t('Stacked Style'), - renderTrigger: true, - choices: [ - ['stack', t('stack')], - ['stream', t('stream')], - ['expand', t('expand')], - ], - default: 'stack', - description: '', - }, - }, - ], - ['color_scheme'], - [richTooltip, showControls], - ], - }, - { - label: t('X Axis'), - tabOverride: 'customize', - expanded: true, - controlSetRows: [ - [xAxisLabel, bottomMargin], - [xTicksLayout, xAxisFormat], - [xAxisShowMinmax, null], - ], - }, - { - label: t('Y Axis'), - tabOverride: 'customize', - expanded: true, - controlSetRows: [ - ['y_axis_format', yAxisBounds], - [yLogScale, null], - ], - }, - timeSeriesSection[1], - sections.annotations, - ], -}; - -export default config; diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/example1.jpg b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/example1.jpg deleted file mode 100644 index 0887fa8fef045..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/example1.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/example2.jpg b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/example2.jpg deleted file mode 100644 index 9fa66adbab69c..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/example2.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/example3.jpg b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/example3.jpg deleted file mode 100644 index 1bd3cedda6b57..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/example3.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/example4.jpg b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/example4.jpg deleted file mode 100644 index b4e1ff07e6685..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/example4.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/thumbnail.png b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/thumbnail.png deleted file mode 100644 index 270ed2339da6e..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/thumbnail.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/thumbnailLarge.png b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/thumbnailLarge.png deleted file mode 100644 index d8b23bea15f00..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/images/thumbnailLarge.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/index.js b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/index.js deleted file mode 100644 index d4af4e40226b2..0000000000000 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Area/index.js +++ /dev/null @@ -1,72 +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 { t, ChartMetadata, ChartPlugin, ChartLabel } from '@superset-ui/core'; -import transformProps from '../transformProps'; -import example1 from './images/example1.jpg'; -import example2 from './images/example2.jpg'; -import example3 from './images/example3.jpg'; -import example4 from './images/example4.jpg'; -import thumbnail from './images/thumbnail.png'; -import { ANNOTATION_TYPES } from '../vendor/superset/AnnotationTypes'; -import controlPanel from './controlPanel'; - -const metadata = new ChartMetadata({ - category: t('Evolution'), - credits: ['http://nvd3.org'], - description: t( - 'A time series chart that visualizes how a related metric from multiple groups vary over time. Each group is visualized using a different color.', - ), - exampleGallery: [ - { url: example1, caption: t('Stretched style') }, - { url: example2, caption: t('Stacked style') }, - { url: example3, caption: t('Video game consoles') }, - { url: example4, caption: t('Vehicle Types') }, - ], - label: ChartLabel.Deprecated, - name: t('Time-series Area Chart (legacy)'), - supportedAnnotationTypes: [ANNOTATION_TYPES.INTERVAL, ANNOTATION_TYPES.EVENT], - tags: [ - t('Comparison'), - t('Continuous'), - t('Legacy'), - t('Line'), - t('Percentages'), - t('Proportional'), - t('Stacked'), - t('Time'), - t('Trend'), - t('nvd3'), - ], - thumbnail, - useLegacyApi: true, -}); - -/** - * @deprecated in version 3.0. - */ -export default class AreaChartPlugin extends ChartPlugin { - constructor() { - super({ - loadChart: () => import('../ReactNVD3'), - metadata, - transformProps, - controlPanel, - }); - } -} diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/controlPanel.ts deleted file mode 100644 index 47fbbd442247b..0000000000000 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/controlPanel.ts +++ /dev/null @@ -1,133 +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 { t } from '@superset-ui/core'; -import { - ControlPanelConfig, - getStandardizedControls, - sections, - sharedControls, -} from '@superset-ui/chart-controls'; -import { - lineInterpolation, - showBrush, - showLegend, - showControls, - xAxisLabel, - yAxisLabel, - bottomMargin, - xTicksLayout, - xAxisFormat, - yLogScale, - yAxisBounds, - xAxisShowMinmax, - yAxisShowMinmax, - richTooltip, - showBarValue, - barStacked, - reduceXTicks, - leftMargin, - timeSeriesSection, -} from '../NVD3Controls'; - -const config: ControlPanelConfig = { - controlOverrides: { - limit: { - rerender: ['timeseries_limit_metric', 'order_desc'], - }, - timeseries_limit_metric: { - label: t('Series Limit Sort By'), - description: t( - 'Metric used to order the limit if a series limit is present. ' + - 'If undefined reverts to the first metric (where appropriate).', - ), - visibility: ({ controls }) => Boolean(controls?.limit.value), - mapStateToProps: (state, controlState) => { - const timeserieslimitProps = - sharedControls.timeseries_limit_metric.mapStateToProps?.( - state, - controlState, - ) || {}; - timeserieslimitProps.value = state.controls?.limit?.value - ? controlState?.value - : []; - return timeserieslimitProps; - }, - }, - order_desc: { - label: t('Series Limit Sort Descending'), - default: false, - description: t( - 'Whether to sort descending or ascending if a series limit is present', - ), - visibility: ({ controls }) => Boolean(controls?.limit.value), - }, - }, - controlPanelSections: [ - sections.legacyTimeseriesTime, - timeSeriesSection[0], - { - label: t('Chart Options'), - expanded: true, - controlSetRows: [ - ['color_scheme'], - [showBrush], - [showLegend], - [showBarValue], - [richTooltip], - [barStacked], - [lineInterpolation], - [showControls], - [bottomMargin], - ], - }, - { - label: t('X Axis'), - expanded: true, - controlSetRows: [ - [xAxisLabel], - [bottomMargin], - [xTicksLayout], - [xAxisFormat], - [xAxisShowMinmax], - [reduceXTicks], - ], - }, - { - label: t('Y Axis'), - expanded: true, - controlSetRows: [ - [yAxisLabel], - [leftMargin], - [yAxisShowMinmax], - [yLogScale], - ['y_axis_format'], - [yAxisBounds], - ], - }, - timeSeriesSection[1], - sections.annotations, - ], - formDataOverrides: formData => ({ - ...formData, - metrics: getStandardizedControls().popAllMetrics(), - groupby: getStandardizedControls().popAllColumns(), - }), -}; - -export default config; diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/Time_Series_Bar_Chart.jpg b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/Time_Series_Bar_Chart.jpg deleted file mode 100644 index f443980b3cae8..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/Time_Series_Bar_Chart.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/Time_Series_Bar_Chart2.jpg b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/Time_Series_Bar_Chart2.jpg deleted file mode 100644 index 3f906a48351b0..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/Time_Series_Bar_Chart2.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/Time_Series_Bar_Chart3.jpg b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/Time_Series_Bar_Chart3.jpg deleted file mode 100644 index 4b48ccea94156..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/Time_Series_Bar_Chart3.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/thumbnail.png b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/thumbnail.png deleted file mode 100644 index e3ef12099392d..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/thumbnail.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/thumbnailLarge.png b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/thumbnailLarge.png deleted file mode 100644 index 35c176e6d1a76..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/images/thumbnailLarge.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/index.js b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/index.js deleted file mode 100644 index c9ac21f84c99f..0000000000000 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/index.js +++ /dev/null @@ -1,65 +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 { t, ChartMetadata, ChartPlugin, ChartLabel } from '@superset-ui/core'; -import transformProps from '../transformProps'; -import thumbnail from './images/thumbnail.png'; -import example1 from './images/Time_Series_Bar_Chart.jpg'; -import example2 from './images/Time_Series_Bar_Chart2.jpg'; -import example3 from './images/Time_Series_Bar_Chart3.jpg'; -import { ANNOTATION_TYPES } from '../vendor/superset/AnnotationTypes'; -import controlPanel from './controlPanel'; - -const metadata = new ChartMetadata({ - category: t('Evolution'), - credits: ['http://nvd3.org'], - description: t( - 'Visualize how a metric changes over time using bars. Add a group by column to visualize group level metrics and how they change over time.', - ), - exampleGallery: [{ url: example1 }, { url: example2 }, { url: example3 }], - label: ChartLabel.Deprecated, - name: t('Time-series Bar Chart (legacy)'), - supportedAnnotationTypes: [ANNOTATION_TYPES.INTERVAL, ANNOTATION_TYPES.EVENT], - tags: [ - t('Bar'), - t('Time'), - t('Trend'), - t('Stacked'), - t('Percentages'), - t('Proportional'), - t('Advanced-Analytics'), - t('nvd3'), - t('Legacy'), - ], - thumbnail, - useLegacyApi: true, -}); - -/** - * @deprecated in version 3.0. - */ -export default class BarChartPlugin extends ChartPlugin { - constructor() { - super({ - loadChart: () => import('../ReactNVD3'), - metadata, - transformProps, - controlPanel, - }); - } -} diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/BoxPlot/images/thumbnail.png b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/BoxPlot/images/thumbnail.png deleted file mode 100644 index ece94d08073ce..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/BoxPlot/images/thumbnail.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/BoxPlot/images/thumbnailLarge.png b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/BoxPlot/images/thumbnailLarge.png deleted file mode 100644 index 35eff285e3d46..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/BoxPlot/images/thumbnailLarge.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/BoxPlot/index.js b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/BoxPlot/index.js deleted file mode 100644 index 1f5a5e99dbdb7..0000000000000 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/BoxPlot/index.js +++ /dev/null @@ -1,39 +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 { t, ChartMetadata, ChartPlugin } from '@superset-ui/core'; -import transformProps from '../transformProps'; -import thumbnail from './images/thumbnail.png'; - -const metadata = new ChartMetadata({ - credits: ['http://nvd3.org'], - description: '', - name: t('Box Plot'), - thumbnail, - useLegacyApi: true, -}); - -export default class BoxPlotChartPlugin extends ChartPlugin { - constructor() { - super({ - loadChart: () => import('../ReactNVD3'), - metadata, - transformProps, - }); - } -} diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/controlPanel.ts deleted file mode 100644 index 9b992897bb2f0..0000000000000 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/controlPanel.ts +++ /dev/null @@ -1,152 +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 { ensureIsArray, t, validateNonEmpty } from '@superset-ui/core'; -import { - ColumnMeta, - ControlPanelConfig, - sharedControls, - getStandardizedControls, -} from '@superset-ui/chart-controls'; -import { - showLegend, - showControls, - xAxisLabel, - bottomMargin, - xTicksLayout, - showBarValue, - barStacked, - reduceXTicks, - yAxisLabel, - yAxisShowMinmax, - yAxisBounds, - richTooltip, -} from '../NVD3Controls'; - -const config: ControlPanelConfig = { - controlPanelSections: [ - { - label: t('Query'), - expanded: true, - controlSetRows: [ - ['metrics'], - ['adhoc_filters'], - ['groupby'], - ['columns'], - ['row_limit'], - ['timeseries_limit_metric'], - ['order_desc'], - [ - { - name: 'contribution', - config: { - type: 'CheckboxControl', - label: t('Contribution'), - default: false, - description: t('Compute the contribution to the total'), - }, - }, - ], - ], - }, - { - label: t('Chart Options'), - expanded: true, - controlSetRows: [ - ['color_scheme'], - [showLegend], - [showBarValue], - [richTooltip], - [barStacked], - [ - { - name: 'order_bars', - config: { - type: 'CheckboxControl', - label: t('Sort Bars'), - default: false, - renderTrigger: true, - description: t('Sort bars by x labels.'), - }, - }, - ], - ['y_axis_format'], - [yAxisLabel], - [showControls, null], - [yAxisShowMinmax], - [yAxisBounds], - ], - }, - { - label: t('X Axis'), - expanded: true, - controlSetRows: [ - [xAxisLabel], - [bottomMargin], - [xTicksLayout], - [reduceXTicks], - ], - }, - ], - controlOverrides: { - groupby: { - validators: [validateNonEmpty], - mapStateToProps: (state, controlState) => { - const groupbyProps = - sharedControls.groupby.mapStateToProps?.(state, controlState) || {}; - groupbyProps.canDropValue = (column: ColumnMeta) => - !ensureIsArray(state.controls?.columns?.value).includes( - column.column_name, - ); - return groupbyProps; - }, - rerender: ['columns'], - }, - columns: { - label: t('Breakdowns'), - description: t('Defines how each series is broken down'), - mapStateToProps: (state, controlState) => { - const columnsProps = - sharedControls.columns.mapStateToProps?.(state, controlState) || {}; - columnsProps.canDropValue = (column: ColumnMeta) => - !ensureIsArray(state.controls?.groupby?.value).includes( - column.column_name, - ); - return columnsProps; - }, - rerender: ['groupby'], - }, - }, - formDataOverrides: formData => { - const columns = getStandardizedControls().controls.columns.filter( - col => !ensureIsArray(formData.groupby).includes(col), - ); - getStandardizedControls().controls.columns = - getStandardizedControls().controls.columns.filter( - col => !columns.includes(col), - ); - - return { - ...formData, - metrics: getStandardizedControls().popAllMetrics(), - columns, - }; - }, -}; - -export default config; diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/BarChart3.jpg b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/BarChart3.jpg deleted file mode 100644 index f9d9bae8ae8c4..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/BarChart3.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/Bar_Chart.jpg b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/Bar_Chart.jpg deleted file mode 100644 index ada903df02d11..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/Bar_Chart.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/Bar_Chart_2.jpg b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/Bar_Chart_2.jpg deleted file mode 100644 index 35eee2cbc7916..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/Bar_Chart_2.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/thumbnail.png b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/thumbnail.png deleted file mode 100644 index 8e9e6b600a145..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/thumbnail.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/thumbnailLarge.png b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/thumbnailLarge.png deleted file mode 100644 index bfaeeb2dfbcc5..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/images/thumbnailLarge.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/index.js b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/index.js deleted file mode 100644 index a505b8398bf44..0000000000000 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/DistBar/index.js +++ /dev/null @@ -1,66 +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 { t, ChartMetadata, ChartPlugin, ChartLabel } from '@superset-ui/core'; -import transformProps from '../transformProps'; -import thumbnail from './images/thumbnail.png'; -import example1 from './images/Bar_Chart.jpg'; -import example2 from './images/Bar_Chart_2.jpg'; -import example3 from './images/BarChart3.jpg'; -import controlPanel from './controlPanel'; - -const metadata = new ChartMetadata({ - category: t('Ranking'), - credits: ['http://nvd3.org'], - description: t( - 'Compares metrics from different categories using bars. Bar lengths are used to indicate the magnitude of each value and color is used to differentiate groups.', - ), - exampleGallery: [ - { url: example1, caption: 'Stacked style' }, - { url: example2, caption: 'Grouped style' }, - { url: example3 }, - ], - label: ChartLabel.Deprecated, - name: t('Bar Chart (legacy)'), - tags: [ - t('Additive'), - t('Bar'), - t('Categorical'), - t('Comparison'), - t('Legacy'), - t('Percentages'), - t('Stacked'), - t('nvd3'), - ], - thumbnail, - useLegacyApi: true, -}); - -/** - * @deprecated in version 3.0. - */ -export default class DistBarChartPlugin extends ChartPlugin { - constructor() { - super({ - loadChart: () => import('../ReactNVD3'), - metadata, - transformProps, - controlPanel, - }); - } -} diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/controlPanel.ts deleted file mode 100644 index 9662cc11d8388..0000000000000 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/controlPanel.ts +++ /dev/null @@ -1,110 +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 { t } from '@superset-ui/core'; -import { - ControlPanelConfig, - sections, - getStandardizedControls, -} from '@superset-ui/chart-controls'; -import { - lineInterpolation, - showBrush, - showLegend, - xAxisLabel, - bottomMargin, - xTicksLayout, - xAxisFormat, - yLogScale, - yAxisBounds, - yAxisLabel, - xAxisShowMinmax, - yAxisShowMinmax, - richTooltip, - leftMargin, - showMarkers, - timeSeriesSection, -} from '../NVD3Controls'; - -const config: ControlPanelConfig = { - controlPanelSections: [ - sections.legacyTimeseriesTime, - timeSeriesSection[0], - { - label: t('Chart Options'), - expanded: true, - controlSetRows: [ - ['color_scheme'], - [showBrush], - [ - { - name: 'send_time_range', - config: { - type: 'CheckboxControl', - label: t('Propagate'), - renderTrigger: true, - default: false, - description: t('Send range filter events to other charts'), - }, - }, - ], - [showLegend], - [richTooltip], - [showMarkers], - [lineInterpolation], - ], - }, - { - label: t('X Axis'), - expanded: true, - controlSetRows: [ - [xAxisLabel], - [bottomMargin], - [xTicksLayout], - [xAxisFormat], - [xAxisShowMinmax, null], - ], - }, - { - label: t('Y Axis'), - expanded: true, - controlSetRows: [ - [yAxisLabel], - [leftMargin], - [yAxisShowMinmax], - [yLogScale], - ['y_axis_format'], - [yAxisBounds], - ], - }, - timeSeriesSection[1], - sections.annotations, - ], - controlOverrides: { - row_limit: { - default: 50000, - }, - }, - formDataOverrides: formData => ({ - ...formData, - metrics: getStandardizedControls().popAllMetrics(), - groupby: getStandardizedControls().popAllColumns(), - }), -}; - -export default config; diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/LineChart.jpg b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/LineChart.jpg deleted file mode 100644 index f141007b4d8d9..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/LineChart.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/LineChart2.jpg b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/LineChart2.jpg deleted file mode 100644 index 994b632be0b78..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/LineChart2.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/battery.jpg b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/battery.jpg deleted file mode 100644 index d6fb43219d827..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/battery.jpg and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/thumbnail.png b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/thumbnail.png deleted file mode 100644 index 61a0d866aa5cf..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/thumbnail.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/thumbnailLarge.png b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/thumbnailLarge.png deleted file mode 100644 index 8cae308c3e4e3..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/images/thumbnailLarge.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/index.js b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/index.js deleted file mode 100644 index 82bf21447d289..0000000000000 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Line/index.js +++ /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 { t, ChartMetadata, ChartPlugin, ChartLabel } from '@superset-ui/core'; -import transformProps from '../transformProps'; -import example1 from './images/LineChart.jpg'; -import example2 from './images/LineChart2.jpg'; -import battery from './images/battery.jpg'; -import thumbnail from './images/thumbnail.png'; -import { ANNOTATION_TYPES } from '../vendor/superset/AnnotationTypes'; -import controlPanel from './controlPanel'; - -const metadata = new ChartMetadata({ - canBeAnnotationTypes: [ANNOTATION_TYPES.TIME_SERIES], - category: t('Evolution'), - credits: ['http://nvd3.org'], - description: t('Classic chart that visualizes how metrics change over time.'), - exampleGallery: [ - { url: example1 }, - { url: example2 }, - { url: battery, caption: t('Battery level over time') }, - ], - label: ChartLabel.Deprecated, - name: t('Time-series Line Chart (legacy)'), - supportedAnnotationTypes: [ - ANNOTATION_TYPES.TIME_SERIES, - ANNOTATION_TYPES.INTERVAL, - ANNOTATION_TYPES.EVENT, - ANNOTATION_TYPES.FORMULA, - ], - tags: [t('Legacy'), t('nvd3')], - thumbnail, - useLegacyApi: true, -}); - -/** - * @deprecated in version 3.0. - */ -export default class LineChartPlugin extends ChartPlugin { - constructor() { - super({ - loadChart: () => import('../ReactNVD3'), - metadata, - transformProps, - controlPanel, - }); - } -} diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/NVD3Vis.js b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/NVD3Vis.js index 5b70c24a37cb6..0620103c7f844 100644 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/NVD3Vis.js +++ b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/NVD3Vis.js @@ -44,21 +44,16 @@ import ANNOTATION_TYPES, { import isTruthy from './utils/isTruthy'; import { cleanColorInput, - computeBarChartWidth, computeYDomain, - computeStackedYDomain, drawBarValues, generateBubbleTooltipContent, generateCompareTooltipContent, - generateRichLineTooltipContent, generateTimePivotTooltip, generateTooltipClassName, - generateAreaChartTooltipContent, getMaxLabelSize, getTimeOrNumberFormatter, hideTooltips, tipFactory, - tryNumify, removeTooltip, setAxisShowMaxMin, stringifyTimeRange, @@ -129,13 +124,7 @@ const BREAKPOINTS = { small: 340, }; -const TIMESERIES_VIZ_TYPES = [ - VizType.LegacyLine, - VizType.LegacyArea, - VizType.Compare, - VizType.LegacyBar, - VizType.TimePivot, -]; +const TIMESERIES_VIZ_TYPES = [VizType.Compare, VizType.TimePivot]; const CHART_ID_PREFIX = 'chart-id-'; @@ -189,17 +178,12 @@ const propTypes = { onError: PropTypes.func, showLegend: PropTypes.bool, showMarkers: PropTypes.bool, - useRichTooltip: PropTypes.bool, vizType: PropTypes.oneOf([ - VizType.LegacyArea, - VizType.LegacyBar, VizType.BoxPlot, 'bubble', VizType.Bullet, VizType.Compare, 'column', - VizType.DistBar, - VizType.LegacyLine, VizType.TimePivot, 'pie', ]), @@ -214,15 +198,9 @@ const propTypes = { yAxisLabel: PropTypes.string, yAxisShowMinMax: PropTypes.bool, yIsLogScale: PropTypes.bool, - // 'dist-bar' only - orderBars: PropTypes.bool, // 'bar' or 'dist-bar' isBarStacked: PropTypes.bool, showBarValue: PropTypes.bool, - // 'bar', 'dist-bar' or 'column' - reduceXTicks: PropTypes.bool, - // 'bar', 'dist-bar' or 'area' - showControls: PropTypes.bool, // 'line' only showBrush: PropTypes.oneOf([true, 'yes', false, 'no', 'auto']), onBrushEnd: PropTypes.func, @@ -242,8 +220,6 @@ const propTypes = { 'key_value_percent', ]), showLabels: PropTypes.bool, - // 'area' only - areaStackedStyle: PropTypes.string, // 'bubble' only entity: PropTypes.string, maxBubbleSize: PropTypes.number, @@ -264,7 +240,6 @@ function nvd3Vis(element, props) { height: maxHeight, annotationData, annotationLayers = [], - areaStackedStyle, baseColor, bottomMargin, colorScheme, @@ -283,19 +258,15 @@ function nvd3Vis(element, props) { maxBubbleSize, onBrushEnd = NOOP, onError = NOOP, - orderBars, pieLabelType, rangeLabels, ranges, - reduceXTicks = false, showBarValue, showBrush, - showControls, showLabels, showLegend, showMarkers, sizeField, - useRichTooltip, vizType, xAxisFormat, numberFormat, @@ -332,7 +303,7 @@ function nvd3Vis(element, props) { } let chart; - let width = maxWidth; + const width = maxWidth; let colorKey = 'key'; container.style.width = `${maxWidth}px`; @@ -356,11 +327,7 @@ function nvd3Vis(element, props) { // Handling xAxis ticks settings const staggerLabels = xTicksLayout === 'staggered'; - const xLabelRotation = - (xTicksLayout === 'auto' && isVizTypes(['column', VizType.DistBar])) || - xTicksLayout === '45°' - ? 45 - : 0; + const xLabelRotation = xTicksLayout === '45°' ? 45 : 0; if (xLabelRotation === 45 && isTruthy(showBrush)) { onError( t('You cannot use 45° tick layout along with the time range filter'), @@ -377,68 +344,12 @@ function nvd3Vis(element, props) { const numberFormatter = getNumberFormatter(numberFormat); switch (vizType) { - case VizType.LegacyLine: - if (canShowBrush) { - chart = nv.models.lineWithFocusChart(); - if (staggerLabels) { - // Give a bit more room to focus area if X axis ticks are staggered - chart.focus.margin({ bottom: 40 }); - chart.focusHeight(80); - } - chart.focus.xScale(d3.time.scale.utc()); - } else { - chart = nv.models.lineChart(); - } - chart.xScale(d3.time.scale.utc()); - chart.interpolate(lineInterpolation); - chart.clipEdge(false); - break; - case VizType.TimePivot: chart = nv.models.lineChart(); chart.xScale(d3.time.scale.utc()); chart.interpolate(lineInterpolation); break; - case VizType.LegacyBar: - chart = nv.models - .multiBarChart() - .showControls(showControls) - .groupSpacing(0.1); - - if (!reduceXTicks) { - width = computeBarChartWidth(data, isBarStacked, maxWidth); - } - chart.width(width); - chart.xAxis.showMaxMin(false); - chart.stacked(isBarStacked); - break; - - case VizType.DistBar: - chart = nv.models - .multiBarChart() - .showControls(showControls) - .reduceXTicks(reduceXTicks) - .groupSpacing(0.1); // Distance between each group of bars. - - chart.xAxis.showMaxMin(false); - - chart.stacked(isBarStacked); - if (orderBars) { - data.forEach(d => { - const newValues = [...d.values]; // need to copy values to avoid redux store changed. - // eslint-disable-next-line no-param-reassign - d.values = newValues.sort((a, b) => - tryNumify(a.x) < tryNumify(b.x) ? -1 : 1, - ); - }); - } - if (!reduceXTicks) { - width = computeBarChartWidth(data, isBarStacked, maxWidth); - } - chart.width(width); - break; - case VizType.Pie: chart = nv.models.pieChart(); colorKey = 'x'; @@ -518,13 +429,6 @@ function nvd3Vis(element, props) { ]); break; - case VizType.LegacyArea: - chart = nv.models.stackedAreaChart(); - chart.showControls(showControls); - chart.style(areaStackedStyle); - chart.xScale(d3.time.scale.utc()); - break; - case VizType.BoxPlot: colorKey = 'label'; chart = nv.models.boxPlotChart(); @@ -608,7 +512,7 @@ function nvd3Vis(element, props) { chart.x2Axis.tickFormat(xAxisFormatter); } if (chart.xAxis && chart.xAxis.tickFormat) { - const isXAxisString = isVizTypes([VizType.DistBar, VizType.BoxPlot]); + const isXAxisString = isVizTypes([VizType.BoxPlot]); if (isXAxisString) { chart.xAxis.tickFormat(d => d.length > MAX_NO_CHARACTERS_IN_LABEL @@ -672,41 +576,6 @@ function nvd3Vis(element, props) { ); } - if ( - isVizTypes([ - VizType.LegacyLine, - VizType.LegacyArea, - VizType.LegacyBar, - VizType.DistBar, - ]) && - useRichTooltip - ) { - chart.useInteractiveGuideline(true); - if (vizType === VizType.LegacyLine || vizType === VizType.LegacyBar) { - chart.interactiveLayer.tooltip.contentGenerator(d => - generateRichLineTooltipContent( - d, - smartDateVerboseFormatter, - yAxisFormatter, - ), - ); - } else if (vizType === VizType.DistBar) { - chart.interactiveLayer.tooltip.contentGenerator(d => - generateCompareTooltipContent(d, yAxisFormatter), - ); - } else { - // area chart - chart.interactiveLayer.tooltip.contentGenerator(d => - generateAreaChartTooltipContent( - d, - smartDateVerboseFormatter, - yAxisFormatter, - chart, - ), - ); - } - } - if (isVizTypes([VizType.Compare])) { chart.interactiveLayer.tooltip.contentGenerator(d => generateCompareTooltipContent(d, yAxisFormatter), @@ -748,43 +617,13 @@ function nvd3Vis(element, props) { const hasCustomMin = isDefined(customMin) && !Number.isNaN(customMin); const hasCustomMax = isDefined(customMax) && !Number.isNaN(customMax); - if ( - (hasCustomMin || hasCustomMax) && - vizType === VizType.LegacyArea && - chart.style() === 'expand' - ) { - // Because there are custom bounds, we need to override them back to 0%-100% since this - // is an expanded area chart - chart.yDomain([0, 1]); - } else if ( - (hasCustomMin || hasCustomMax) && - vizType === VizType.LegacyArea && - chart.style() === 'stream' - ) { - // Because there are custom bounds, we need to override them back to the domain of the - // data since this is a stream area chart - chart.yDomain(computeStackedYDomain(data)); - } else if (hasCustomMin && hasCustomMax) { + if (hasCustomMin && hasCustomMax) { // Override the y domain if there's both a custom min and max chart.yDomain([customMin, customMax]); chart.clipEdge(true); } else if (hasCustomMin || hasCustomMax) { // Only one of the bounds has been set, so we need to manually calculate the other one - let [trueMin, trueMax] = [0, 1]; - - // These viz types can be stacked - // They correspond to the nvd3 stackedAreaChart and multiBarChart - if ( - vizType === VizType.LegacyArea || - (isVizTypes([VizType.LegacyBar, VizType.DistBar]) && - chart.stacked()) - ) { - // This is a stacked area chart or a stacked bar chart - [trueMin, trueMax] = computeStackedYDomain(data); - } else { - [trueMin, trueMax] = computeYDomain(data); - } - + const [trueMin, trueMax] = computeYDomain(data); const min = hasCustomMin ? customMin : trueMin; const max = hasCustomMax ? customMax : trueMax; chart.yDomain([min, max]); @@ -955,26 +794,15 @@ function nvd3Vis(element, props) { a => a.annotationType === ANNOTATION_TYPES.FORMULA, ); - let xMax; - let xMin; + const xMax = chart.xAxis.scale().domain()[1].valueOf(); + const xMin = chart.xAxis.scale().domain()[0].valueOf(); let xScale; - if (vizType === VizType.LegacyBar) { - xMin = d3.min(data[0].values, d => d.x); - xMax = d3.max(data[0].values, d => d.x); - xScale = d3.scale - .quantile() - .domain([xMin, xMax]) - .range(chart.xAxis.range()); + if (chart.xScale) { + xScale = chart.xScale(); + } else if (chart.xAxis.scale) { + xScale = chart.xAxis.scale(); } else { - xMin = chart.xAxis.scale().domain()[0].valueOf(); - xMax = chart.xAxis.scale().domain()[1].valueOf(); - if (chart.xScale) { - xScale = chart.xScale(); - } else if (chart.xAxis.scale) { - xScale = chart.xAxis.scale(); - } else { - xScale = d3.scale.linear(); - } + xScale = d3.scale.linear(); } if (xScale && xScale.clamp) { xScale.clamp(true); @@ -982,36 +810,21 @@ function nvd3Vis(element, props) { if (formulas.length > 0) { const xValues = []; - if (vizType === VizType.LegacyBar) { - // For bar-charts we want one data point evaluated for every - // data point that will be displayed. - const distinct = data.reduce((xVals, d) => { - d.values.forEach(x => xVals.add(x.x)); - - return xVals; - }, new Set()); - xValues.push(...distinct.values()); - xValues.sort(); - } else { - // For every other time visualization it should be ok, to have a - // data points in even intervals. - let period = Math.min( - ...data.map(d => - Math.min( - ...d.values.slice(1).map((v, i) => v.x - d.values[i].x), - ), - ), - ); - const dataPoints = (xMax - xMin) / (period || 1); - // make sure that there are enough data points and not too many - period = dataPoints < 100 ? (xMax - xMin) / 100 : period; - period = dataPoints > 500 ? (xMax - xMin) / 500 : period; - xValues.push(xMin); - for (let x = xMin; x < xMax; x += period) { - xValues.push(x); - } - xValues.push(xMax); + let period = Math.min( + ...data.map(d => + Math.min(...d.values.slice(1).map((v, i) => v.x - d.values[i].x)), + ), + ); + const dataPoints = (xMax - xMin) / (period || 1); + // make sure that there are enough data points and not too many + period = dataPoints < 100 ? (xMax - xMin) / 100 : period; + period = dataPoints > 500 ? (xMax - xMin) / 500 : period; + xValues.push(xMin); + for (let x = xMin; x < xMax; x += period) { + xValues.push(x); } + xValues.push(xMax); + const formulaData = formulas.map(fo => { const { value: expression } = fo; return { diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Pie/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Pie/controlPanel.ts deleted file mode 100644 index ca4bf66062aec..0000000000000 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Pie/controlPanel.ts +++ /dev/null @@ -1,125 +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 { t } from '@superset-ui/core'; -import { - ControlPanelConfig, - D3_FORMAT_DOCS, - D3_FORMAT_OPTIONS, - D3_NUMBER_FORMAT_DESCRIPTION_PERCENTAGE_TEXT, -} from '@superset-ui/chart-controls'; -import { showLegend } from '../NVD3Controls'; - -const config: ControlPanelConfig = { - controlPanelSections: [ - { - label: t('Query'), - expanded: true, - controlSetRows: [ - ['groupby'], - ['metric'], - ['adhoc_filters'], - ['row_limit'], - ], - }, - { - label: t('Chart Options'), - expanded: true, - controlSetRows: [ - [ - { - name: 'pie_label_type', - config: { - type: 'SelectControl', - label: t('Label Type'), - default: 'key', - renderTrigger: true, - choices: [ - ['key', t('Category Name')], - ['value', t('Value')], - ['percent', t('Percentage')], - ['key_value', t('Category and Value')], - ['key_percent', t('Category and Percentage')], - ['key_value_percent', t('Category, Value and Percentage')], - ], - description: t('What should be shown on the label?'), - }, - }, - { - name: 'number_format', - config: { - type: 'SelectControl', - freeForm: true, - label: t('Number format'), - renderTrigger: true, - default: 'SMART_NUMBER', - choices: D3_FORMAT_OPTIONS, - description: `${D3_FORMAT_DOCS} ${D3_NUMBER_FORMAT_DESCRIPTION_PERCENTAGE_TEXT}`, - }, - }, - ], - [ - { - name: 'donut', - config: { - type: 'CheckboxControl', - label: t('Donut'), - default: false, - renderTrigger: true, - description: t('Do you want a donut or a pie?'), - }, - }, - showLegend, - ], - [ - { - name: 'show_labels', - config: { - type: 'CheckboxControl', - label: t('Show Labels'), - renderTrigger: true, - default: true, - description: t( - 'Whether to display the labels. Note that the label only displays when the 5% ' + - 'threshold.', - ), - }, - }, - { - name: 'labels_outside', - config: { - type: 'CheckboxControl', - label: t('Put labels outside'), - default: true, - renderTrigger: true, - description: t('Put the labels outside the pie?'), - }, - }, - ], - ['color_scheme'], - ], - }, - ], - controlOverrides: { - row_limit: { - default: 25, - }, - }, -}; - -export default config; diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Pie/images/thumbnail.png b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Pie/images/thumbnail.png deleted file mode 100644 index 8dcb780d0999c..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Pie/images/thumbnail.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Pie/images/thumbnailLarge.png b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Pie/images/thumbnailLarge.png deleted file mode 100644 index 8115f25f1775b..0000000000000 Binary files a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Pie/images/thumbnailLarge.png and /dev/null differ diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Pie/index.js b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Pie/index.js deleted file mode 100644 index 903ad154da604..0000000000000 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Pie/index.js +++ /dev/null @@ -1,46 +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 { t, ChartMetadata, ChartPlugin, ChartLabel } from '@superset-ui/core'; -import transformProps from '../transformProps'; -import thumbnail from './images/thumbnail.png'; -import controlPanel from './controlPanel'; - -const metadata = new ChartMetadata({ - credits: ['http://nvd3.org'], - description: '', - label: ChartLabel.Deprecated, - name: t('Pie Chart (legacy)'), - thumbnail, - useLegacyApi: true, - tags: [t('Legacy'), t('nvd3')], -}); - -/** - * @deprecated in version 3.0. - */ -export default class PieChartPlugin extends ChartPlugin { - constructor() { - super({ - loadChart: () => import('../ReactNVD3'), - metadata, - transformProps, - controlPanel, - }); - } -} diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/index.js b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/index.js index 4c7eeef5756b2..2b26d4d67db9d 100644 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/index.js +++ b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/index.js @@ -18,13 +18,7 @@ */ export { default as NVD3ChartPreset } from './preset'; -export { default as AreaChartPlugin } from './Area'; -export { default as BarChartPlugin } from './Bar'; -export { default as BoxPlotChartPlugin } from './BoxPlot'; export { default as BubbleChartPlugin } from './Bubble'; export { default as BulletChartPlugin } from './Bullet'; export { default as CompareChartPlugin } from './Compare'; -export { default as DistBarChartPlugin } from './DistBar'; -export { default as LineChartPlugin } from './Line'; -export { default as PieChartPlugin } from './Pie'; export { default as TimePivotChartPlugin } from './TimePivot'; diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/preset.js b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/preset.js index a99c268fd4773..09a636b393422 100644 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/preset.js +++ b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/preset.js @@ -17,15 +17,9 @@ * under the License. */ import { Preset, VizType } from '@superset-ui/core'; -import AreaChartPlugin from './Area'; -import BarChartPlugin from './Bar'; -import BoxPlotChartPlugin from './BoxPlot'; import BubbleChartPlugin from './Bubble'; import BulletChartPlugin from './Bullet'; import CompareChartPlugin from './Compare'; -import DistBarChartPlugin from './DistBar'; -import LineChartPlugin from './Line'; -import PieChartPlugin from './Pie'; import TimePivotChartPlugin from './TimePivot'; export default class NVD3ChartPreset extends Preset { @@ -33,15 +27,9 @@ export default class NVD3ChartPreset extends Preset { super({ name: 'NVD3 charts', plugins: [ - new AreaChartPlugin().configure({ key: VizType.LegacyArea }), - new BarChartPlugin().configure({ key: VizType.LegacyBar }), - new BoxPlotChartPlugin().configure({ key: VizType.BoxPlot }), new BubbleChartPlugin().configure({ key: VizType.LegacyBubble }), new BulletChartPlugin().configure({ key: VizType.Bullet }), new CompareChartPlugin().configure({ key: VizType.Compare }), - new DistBarChartPlugin().configure({ key: VizType.DistBar }), - new LineChartPlugin().configure({ key: VizType.LegacyLine }), - new PieChartPlugin().configure({ key: VizType.Pie }), new TimePivotChartPlugin().configure({ key: VizType.TimePivot }), ], }); diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/transformProps.js b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/transformProps.js index 4bd1fbd1748fe..36465e97a438e 100644 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/transformProps.js +++ b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/transformProps.js @@ -66,7 +66,6 @@ export default function transformProps(chartProps) { lineInterpolation, maxBubbleSize, metric, - metrics = [], orderBars, pieLabelType, reduceXTicks, @@ -91,6 +90,7 @@ export default function transformProps(chartProps) { yAxisBounds, yAxis2Bounds, yAxisLabel, + yAxisFormat, yAxisShowminmax, yAxis2Showminmax, yLogScale, @@ -105,7 +105,6 @@ export default function transformProps(chartProps) { numberFormat, rangeLabels, ranges, - yAxisFormat, } = formData; const rawData = queriesData[0].data || []; @@ -121,17 +120,6 @@ export default function transformProps(chartProps) { if (vizType === VizType.Pie) { numberFormat = numberFormat || grabD3Format(datasource, metric); - } else if ( - [ - VizType.LegacyLine, - VizType.DistBar, - VizType.LegacyBar, - VizType.LegacyArea, - ].includes(chartProps.formData.vizType) - ) { - yAxisFormat = - yAxisFormat || - grabD3Format(datasource, metrics.length > 0 ? metrics[0] : undefined); } else if (vizType === VizType.Bullet) { ranges = tokenizeToNumericArray(ranges) || [0, data.measures * 1.1]; rangeLabels = tokenizeToStringArray(rangeLabels); diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/utils.js b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/utils.js index e31f39be23a1c..dccc9e4184a50 100644 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/utils.js +++ b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/utils.js @@ -111,41 +111,6 @@ function getFormattedKey(seriesKey, shouldDompurify) { return shouldDompurify ? dompurify.sanitize(seriesKey) : seriesKey; } -// Custom sorted tooltip -// use a verbose formatter for times -export function generateRichLineTooltipContent( - d, - timeFormatter, - valueFormatter, -) { - let tooltip = ''; - tooltip += - "'; - d.series.sort((a, b) => (a.value >= b.value ? -1 : 1)); - d.series.forEach(series => { - const key = getFormattedKey(series.key, true); - tooltip += - `` + - `' + - `` + - `` + - ''; - }); - tooltip += '
" + - `${timeFormatter(d.value)}` + - '
` + - '
' + - '
${key}${valueFormatter(series.value)}
'; - - return dompurify.sanitize(tooltip); -} - export function generateCompareTooltipContent(d, valueFormatter) { let tooltip = ''; tooltip += @@ -175,46 +140,6 @@ export function generateCompareTooltipContent(d, valueFormatter) { return dompurify.sanitize(tooltip); } -export function generateAreaChartTooltipContent( - d, - timeFormatter, - valueFormatter, - chart, -) { - const total = - chart.style() === 'expand' - ? // expand mode does not include total row - d3.sum(d.series, s => s.value) - : // other modes include total row at the end - d.series[d.series.length - 1].value; - let tooltip = ''; - tooltip += - "' + - ''; - d.series.forEach(series => { - const key = getFormattedKey(series.key, true); - const isTotal = series.key === 'TOTAL'; - let trClass = ''; - if (series.highlight) { - trClass = 'superset-legacy-chart-nvd3-tr-highlight'; - } else if (isTotal) { - trClass = 'superset-legacy-chart-nvd3-tr-total'; - } - tooltip += - `` + - `` + - `` + - `` + - `` + - ''; - }); - tooltip += '
" + - `${timeFormatter(d.value)}` + - '
CategoryValue% to total
${isTotal ? '' : '◼'}${key}${valueFormatter(isTotal ? total : series?.point?.y)}${((100 * series.value) / total).toFixed(2)}%
'; - - return dompurify.sanitize(tooltip); -} - export function generateMultiLineTooltipContent(d, xFormatter, yFormatters) { const tooltipTitle = xFormatter(d.value); let tooltip = ''; @@ -392,25 +317,6 @@ export function formatLabel(input, verboseMap = {}) { : verboseLookup(input); } -const MIN_BAR_WIDTH = 18; - -export function computeBarChartWidth(data, stacked, maxWidth) { - const barCount = stacked - ? d3.max(data, d => d.values.length) - : d3.sum(data, d => d.values.length); - - const barWidth = barCount * MIN_BAR_WIDTH; - - return Math.max(barWidth, maxWidth); -} - -export function tryNumify(s) { - // Attempts casting to Number, returns string when failing - const n = Number(s); - - return Number.isNaN(n) ? s : n; -} - export function stringifyTimeRange(extent) { if (extent.some(d => d.toISOString === undefined)) { return null; @@ -438,18 +344,3 @@ export function computeYDomain(data) { return [0, 1]; } - -export function computeStackedYDomain(data) { - if (Array.isArray(data) && data.length > 0 && Array.isArray(data[0].values)) { - const series = data - .filter(d => !d.disabled) - .map(d => d.values.map(v => v.y)); - const stackedValues = series[0].map((_, i) => - series.reduce((acc, cur) => acc + cur[i], 0), - ); - - return [Math.min(0, ...stackedValues), Math.max(0, ...stackedValues)]; - } - - return [0, 1]; -} diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/test/utils.test.js b/superset-frontend/plugins/legacy-preset-chart-nvd3/test/utils.test.js index c4bb078fc7d79..137a20e6256af 100644 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/test/utils.test.js +++ b/superset-frontend/plugins/legacy-preset-chart-nvd3/test/utils.test.js @@ -23,11 +23,9 @@ import { } from '@superset-ui/core'; import { - computeStackedYDomain, computeYDomain, getTimeOrNumberFormatter, formatLabel, - tryNumify, } from '../src/utils'; const DATA = [ @@ -167,15 +165,6 @@ describe('nvd3/utils', () => { }); }); - describe('tryNumify()', () => { - it('tryNumify works as expected', () => { - expect(tryNumify(5)).toBe(5); - expect(tryNumify('5')).toBe(5); - expect(tryNumify('5.1')).toBe(5.1); - expect(tryNumify('a string')).toBe('a string'); - }); - }); - describe('computeYDomain()', () => { it('works with invalid data', () => { expect(computeYDomain('foo')).toEqual([0, 1]); @@ -191,20 +180,4 @@ describe('nvd3/utils', () => { ]); }); }); - - describe('computeStackedYDomain()', () => { - it('works with invalid data', () => { - expect(computeStackedYDomain('foo')).toEqual([0, 1]); - }); - - it('works with all series enabled', () => { - expect(computeStackedYDomain(DATA)).toEqual([0, 2287437662.0]); - }); - - it('works with some series disabled', () => { - expect(computeStackedYDomain(DATA_WITH_DISABLED_SERIES)).toEqual([ - 0, 668526708.0, - ]); - }); - }); }); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/buildQuery.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/buildQuery.ts index 2d1ee869eb9e6..858a8ddb3e665 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/buildQuery.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/buildQuery.ts @@ -18,6 +18,7 @@ */ import { QueryFormColumn, + QueryFormData, QueryFormOrderBy, buildQueryContext, ensureIsArray, @@ -26,9 +27,8 @@ import { getXAxisColumn, } from '@superset-ui/core'; import { rankOperator } from '@superset-ui/chart-controls'; -import { HeatmapFormData } from './types'; -export default function buildQuery(formData: HeatmapFormData) { +export default function buildQuery(formData: QueryFormData) { const { groupby, normalize_across, sort_x_axis, sort_y_axis, x_axis } = formData; const metric = getMetricLabel(formData.metric); @@ -36,16 +36,19 @@ export default function buildQuery(formData: HeatmapFormData) { ...ensureIsArray(getXAxisColumn(formData)), ...ensureIsArray(groupby), ]; - const orderby: QueryFormOrderBy[] = [ - [ + const orderby: QueryFormOrderBy[] = []; + if (sort_x_axis) { + orderby.push([ sort_x_axis.includes('value') ? metric : columns[0], sort_x_axis.includes('asc'), - ], - [ + ]); + } + if (sort_y_axis) { + orderby.push([ sort_y_axis.includes('value') ? metric : columns[1], sort_y_axis.includes('asc'), - ], - ]; + ]); + } const group_by = normalize_across === 'x' ? getColumnLabel(x_axis) diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/controlPanel.tsx index 16b825e8463bc..bc949acf99525 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/controlPanel.tsx @@ -50,8 +50,7 @@ const config: ControlPanelConfig = { label: t('Sort X Axis'), choices: sortAxisChoices, renderTrigger: false, - clearable: false, - default: 'alpha_asc', + clearable: true, }, }, ], @@ -63,8 +62,7 @@ const config: ControlPanelConfig = { label: t('Sort Y Axis'), choices: sortAxisChoices, renderTrigger: false, - clearable: false, - default: 'alpha_asc', + clearable: true, }, }, ], diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/transformProps.ts index d0b0b1dd7de2f..5a3cd75870094 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/transformProps.ts @@ -156,8 +156,10 @@ export default function transformProps( ), label: { show: showValues, - formatter: (params: CallbackDataParams) => - valueFormatter(params.value?.[2]), + formatter: (params: CallbackDataParams) => { + const paramsValue = params.value as (string | number)[]; + return valueFormatter(paramsValue?.[2] as number | null | undefined); + }, }, }, ]; @@ -178,9 +180,10 @@ export default function transformProps( yAxisLabel, metricLabel, ); - const x = params.value?.[0]; - const y = params.value?.[1]; - const value = params.value?.[2]; + const paramsValue = params.value as (string | number)[]; + const x = paramsValue?.[0]; + const y = paramsValue?.[1]; + const value = paramsValue?.[2] as number | null | undefined; const formattedX = xAxisFormatter(x); const formattedY = yAxisFormatter(y); const formattedValue = valueFormatter(value); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/types.ts index 8ec98470329dd..11b1685e4de58 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/types.ts @@ -36,8 +36,8 @@ export interface HeatmapFormData extends QueryFormData { showLegend?: boolean; showPercentage?: boolean; showValues?: boolean; - sortXAxis: string; - sortYAxis: string; + sortXAxis?: string; + sortYAxis?: string; timeFormat?: string; xAxis: QueryFormColumn; xscaleInterval: number; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts index 5eb0bdd0a2bd2..10f57fb8d87d9 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts @@ -45,7 +45,6 @@ import { extractExtraMetrics, getOriginalSeries, isDerivedSeries, - getTimeOffset, } from '@superset-ui/chart-controls'; import type { EChartsCoreOption } from 'echarts/core'; import type { LineStyleOption } from 'echarts/types/src/util/types'; @@ -281,21 +280,15 @@ export default function transformProps( const array = ensureIsArray(chartProps.rawFormData?.time_compare); const inverted = invert(verboseMap); - const offsetLineWidths: { [key: string]: number } = {}; + let patternIncrement = 0; rawSeries.forEach(entry => { const derivedSeries = isDerivedSeries(entry, chartProps.rawFormData); const lineStyle: LineStyleOption = {}; if (derivedSeries) { - const offset = getTimeOffset( - entry, - ensureIsArray(chartProps.rawFormData?.time_compare), - )!; - if (!offsetLineWidths[offset]) { - offsetLineWidths[offset] = Object.keys(offsetLineWidths).length + 1; - } - lineStyle.type = 'dashed'; - lineStyle.width = offsetLineWidths[offset]; + patternIncrement += 1; + // use a combination of dash and dot for the line style + lineStyle.type = [(patternIncrement % 5) + 1, (patternIncrement % 3) + 1]; lineStyle.opacity = OpacityEnum.DerivedSeries; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts index 30d2509e45067..cadf647484df5 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts @@ -315,15 +315,7 @@ export function transformSeries( opacity: opacity * areaOpacity, } : undefined, - emphasis: { - // bold on hover as required since 5.3.0 to retain backwards feature parity: - // https://apache.github.io/echarts-handbook/en/basics/release-note/5-3-0/#removing-the-default-bolding-emphasis-effect-in-the-line-chart - // TODO: should consider only adding emphasis to currently hovered series - lineStyle: { - width: 'bolder', - }, - ...emphasis, - }, + emphasis, showSymbol, symbolSize: markerSize, label: { diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/forecast.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/forecast.ts index c7244baf48d1b..1f92390113065 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/forecast.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/forecast.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -import { isNumber } from 'lodash'; import { DataRecord, DTTM_ALIAS, ValueFormatter } from '@superset-ui/core'; import type { OptionName } from 'echarts/types/src/util/types'; import type { TooltipMarker } from 'echarts/types/src/util/format'; @@ -64,7 +63,7 @@ export const extractForecastValuesFromTooltipParams = ( const { marker, seriesId, value } = param; const context = extractForecastSeriesContext(seriesId); const numericValue = isHorizontal ? value[0] : value[1]; - if (isNumber(numericValue)) { + if (typeof numericValue === 'number') { if (!(context.name in values)) values[context.name] = { marker: marker || '', @@ -97,7 +96,7 @@ export const formatForecastTooltipSeries = ({ formatter: ValueFormatter; }): string[] => { const name = `${marker}${sanitizeHtml(seriesName)}`; - let value = isNumber(observation) ? formatter(observation) : ''; + let value = typeof observation === 'number' ? formatter(observation) : ''; if (forecastTrend || forecastLower || forecastUpper) { // forecast values take the form of "20, y = 30 (10, 40)" // where the first part is the observation, the second part is the forecast trend diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/treeBuilder.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/treeBuilder.ts index cda78da93ae69..919d6e6db6a8d 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/treeBuilder.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/treeBuilder.ts @@ -17,7 +17,7 @@ * under the License. */ import { DataRecord, DataRecordValue } from '@superset-ui/core'; -import { groupBy as _groupBy, isNumber, transform } from 'lodash'; +import { groupBy as _groupBy, transform } from 'lodash'; export type TreeNode = { name: DataRecordValue; @@ -28,7 +28,7 @@ export type TreeNode = { }; function getMetricValue(datum: DataRecord, metric: string) { - return isNumber(datum[metric]) ? (datum[metric] as number) : 0; + return typeof datum[metric] === 'number' ? (datum[metric] as number) : 0; } export function treeBuilder( diff --git a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx index b9cc335a34f0b..77905ea562edb 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx +++ b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx @@ -61,7 +61,7 @@ import { PlusCircleOutlined, TableOutlined, } from '@ant-design/icons'; -import { isEmpty, isNumber } from 'lodash'; +import { isEmpty } from 'lodash'; import { ColorSchemeEnum, DataColumnMeta, @@ -899,7 +899,9 @@ export default function TableChart( /* The following classes are added to support custom CSS styling */ className={cx( 'cell-bar', - isNumber(value) && value < 0 ? 'negative' : 'positive', + typeof value === 'number' && value < 0 + ? 'negative' + : 'positive', )} css={cellBarStyles} role="presentation" diff --git a/superset-frontend/spec/fixtures/mockSliceEntities.js b/superset-frontend/spec/fixtures/mockSliceEntities.js index 809989b0e71bb..ccff1d4fac2d8 100644 --- a/superset-frontend/spec/fixtures/mockSliceEntities.js +++ b/superset-frontend/spec/fixtures/mockSliceEntities.js @@ -139,7 +139,7 @@ export const sliceEntitiesForDashboard = { slice_url: '/explore/?form_data=%7B%22slice_id%22%3A%20130%7D', slice_name: 'Growth Rate', form_data: {}, - viz_type: VizType.LegacyLine, + viz_type: VizType.Line, datasource: '2__table', description: '', description_markdown: '', @@ -223,7 +223,7 @@ export const sliceEntitiesForDashboard = { slice_url: '/explore/?form_data=%7B%22slice_id%22%3A%20134%7D', slice_name: "World's Pop Growth", form_data: {}, - viz_type: VizType.LegacyArea, + viz_type: VizType.Area, datasource: '2__table', description: '', description_markdown: '', diff --git a/superset-frontend/src/GlobalStyles.tsx b/superset-frontend/src/GlobalStyles.tsx index 4a42c9bfb5704..c305fb214a0a8 100644 --- a/superset-frontend/src/GlobalStyles.tsx +++ b/superset-frontend/src/GlobalStyles.tsx @@ -39,10 +39,13 @@ export const GlobalStyles = () => ( .echarts-tooltip[style*='visibility: hidden'] { display: none !important; } + .ant-popover, .antd5-dropdown, - .ant-dropdown { + .ant-dropdown, + .ant-select-dropdown { z-index: ${theme.zIndex.max}; } + // TODO: Remove when buttons have been upgraded to Ant Design 5. // Check src/components/Modal for more info. .ant-modal-confirm { @@ -96,6 +99,13 @@ export const GlobalStyles = () => ( margin-right: 0; } } + .ant-dropdown-menu-sub .antd5-menu.antd5-menu-vertical { + box-shadow: none; + } + .ant-dropdown-menu-submenu-title, + .ant-dropdown-menu-item { + line-height: 1.5em !important; + } `} /> ); diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.js b/superset-frontend/src/SqlLab/actions/sqlLab.js index 4fd7708338283..d8ec7a19a564e 100644 --- a/superset-frontend/src/SqlLab/actions/sqlLab.js +++ b/superset-frontend/src/SqlLab/actions/sqlLab.js @@ -1167,9 +1167,31 @@ export function persistEditorHeight(queryEditor, northPercent, southPercent) { }; } +export function popPermalink(key) { + return function (dispatch) { + return SupersetClient.get({ endpoint: `/api/v1/sqllab/permalink/${key}` }) + .then(({ json }) => + dispatch( + addQueryEditor({ + name: json.name ? json.name : t('Shared query'), + dbId: json.dbId ? parseInt(json.dbId, 10) : null, + catalog: json.catalog ? json.catalog : null, + schema: json.schema ? json.schema : null, + autorun: json.autorun ? json.autorun : false, + sql: json.sql ? json.sql : 'SELECT ...', + templateParams: json.templateParams, + }), + ), + ) + .catch(() => dispatch(addDangerToast(ERR_MSG_CANT_LOAD_QUERY))); + }; +} + export function popStoredQuery(urlId) { return function (dispatch) { - return SupersetClient.get({ endpoint: `/kv/${urlId}` }) + return SupersetClient.get({ + endpoint: `/api/v1/sqllab/permalink/kv:${urlId}`, + }) .then(({ json }) => dispatch( addQueryEditor({ @@ -1204,6 +1226,7 @@ export function popSavedQuery(saveQueryId) { schema: queryEditorProps.schema, sql: queryEditorProps.sql, templateParams: queryEditorProps.templateParams, + remoteId: queryEditorProps.remoteId, }; return dispatch(addQueryEditor(tmpAdaptedProps)); }) diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.test.js b/superset-frontend/src/SqlLab/actions/sqlLab.test.js index 142c6768d1b20..7591abfaea7ff 100644 --- a/superset-frontend/src/SqlLab/actions/sqlLab.test.js +++ b/superset-frontend/src/SqlLab/actions/sqlLab.test.js @@ -30,6 +30,9 @@ import { initialState, queryId, } from 'src/SqlLab/fixtures'; +import { SupersetClient } from '@superset-ui/core'; +import { ADD_TOAST } from 'src/components/MessageToasts/actions'; +import { ToastType } from '../../components/MessageToasts/types'; const middlewares = [thunk]; const mockStore = configureMockStore(middlewares); @@ -453,6 +456,112 @@ describe('async actions', () => { }); }); + describe('popSavedQuery', () => { + const supersetClientGetSpy = jest.spyOn(SupersetClient, 'get'); + const store = mockStore({}); + + const mockSavedQueryApiResponse = { + catalog: null, + changed_by: { + first_name: 'Superset', + id: 1, + last_name: 'Admin', + }, + changed_on: '2024-12-28T20:06:14.246743', + changed_on_delta_humanized: '8 days ago', + created_by: { + first_name: 'Superset', + id: 1, + last_name: 'Admin', + }, + database: { + database_name: 'examples', + id: 2, + }, + description: '', + id: 1, + label: 'Query 1', + schema: 'public', + sql: 'SELECT * FROM channels', + sql_tables: [ + { + catalog: null, + schema: null, + table: 'channels', + }, + ], + template_parameters: null, + }; + + const makeRequest = id => { + const request = actions.popSavedQuery(id); + const { dispatch } = store; + + return request(dispatch, () => initialState); + }; + + beforeEach(() => { + supersetClientGetSpy.mockClear(); + store.clearActions(); + }); + + afterAll(() => { + supersetClientGetSpy.mockRestore(); + }); + + it('calls API endpint with correct params', async () => { + supersetClientGetSpy.mockResolvedValue({ + json: { result: mockSavedQueryApiResponse }, + }); + + await makeRequest(123); + + expect(supersetClientGetSpy).toHaveBeenCalledWith({ + endpoint: '/api/v1/saved_query/123', + }); + }); + + it('dispatches addQueryEditor with correct params on successful API call', async () => { + supersetClientGetSpy.mockResolvedValue({ + json: { result: mockSavedQueryApiResponse }, + }); + + const expectedParams = { + name: 'Query 1', + dbId: 2, + catalog: null, + schema: 'public', + sql: 'SELECT * FROM channels', + templateParams: null, + remoteId: 1, + }; + + await makeRequest(1); + + const addQueryEditorAction = store + .getActions() + .find(action => action.type === actions.ADD_QUERY_EDITOR); + + expect(addQueryEditorAction).toBeTruthy(); + expect(addQueryEditorAction?.queryEditor).toEqual( + expect.objectContaining(expectedParams), + ); + }); + + it('should dispatch addDangerToast on API error', async () => { + supersetClientGetSpy.mockResolvedValue(new Error()); + + await makeRequest(1); + + const addToastAction = store + .getActions() + .find(action => action.type === ADD_TOAST); + + expect(addToastAction).toBeTruthy(); + expect(addToastAction?.payload?.toastType).toBe(ToastType.Danger); + }); + }); + describe('addQueryEditor', () => { it('creates new query editor', () => { expect.assertions(1); diff --git a/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx b/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx index 98dcd34862f6a..5df4d0c304457 100644 --- a/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx +++ b/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx @@ -50,7 +50,7 @@ import { mountExploreUrl } from 'src/explore/exploreUtils'; import { postFormData } from 'src/explore/exploreUtils/formData'; import { URL_PARAMS } from 'src/constants'; import { SelectValue } from 'antd/lib/select'; -import { isEmpty, isString } from 'lodash'; +import { isEmpty } from 'lodash'; interface QueryDatabase { id?: number; @@ -280,7 +280,7 @@ export const SaveDatasetModal = ({ // Remove the special filters entry from the templateParams // before saving the dataset. let templateParams; - if (isString(datasource?.templateParams)) { + if (typeof datasource?.templateParams === 'string') { const p = JSON.parse(datasource.templateParams); /* eslint-disable-next-line no-underscore-dangle */ if (p._filters) { diff --git a/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx b/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx index 83f49feef5975..7053f5df7be62 100644 --- a/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx +++ b/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx @@ -24,7 +24,7 @@ import validator from '@rjsf/validator-ajv8'; import { Row, Col } from 'src/components'; import { Input, TextArea } from 'src/components/Input'; import { t, styled } from '@superset-ui/core'; -import * as chrono from 'chrono-node'; +import { parseDate } from 'chrono-node'; import ModalTrigger, { ModalTriggerRef } from 'src/components/ModalTrigger'; import { Form, FormItem } from 'src/components/Form'; import Button from 'src/components/Button'; @@ -47,11 +47,10 @@ const getJSONSchema = () => { Object.entries(jsonSchema.properties).forEach( ([key, value]: [string, any]) => { if (value.default && value.format === 'date-time') { + const parsedDate = parseDate(value.default); jsonSchema.properties[key] = { ...value, - default: value.default - ? chrono.parseDate(value.default)?.toISOString() - : null, + default: parsedDate ? parsedDate.toISOString() : null, }; } }, diff --git a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/ShareSqlLabQuery.test.tsx b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/ShareSqlLabQuery.test.tsx index d522d73d90203..ae3c7d561305d 100644 --- a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/ShareSqlLabQuery.test.tsx +++ b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/ShareSqlLabQuery.test.tsx @@ -23,10 +23,9 @@ import fetchMock from 'fetch-mock'; import * as uiCore from '@superset-ui/core'; import { Provider } from 'react-redux'; import { supersetTheme, ThemeProvider } from '@superset-ui/core'; -import { render, screen, act } from '@testing-library/react'; +import { render, screen, act, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; -import * as utils from 'src/utils/common'; import ShareSqlLabQuery from 'src/SqlLab/components/ShareSqlLabQuery'; import { initialState } from 'src/SqlLab/fixtures'; @@ -92,20 +91,24 @@ const standardProviderWithUnsaved: FC = ({ children }) => ( ); describe('ShareSqlLabQuery', () => { - const storeQueryUrl = 'glob:*/kv/store/'; - const storeQueryMockId = '123'; + const storeQueryUrl = 'glob:*/api/v1/sqllab/permalink'; + const storeQueryMockId = 'ci39c3'; beforeEach(async () => { - fetchMock.post(storeQueryUrl, () => ({ id: storeQueryMockId }), { - overwriteRoutes: true, - }); + fetchMock.post( + storeQueryUrl, + () => ({ key: storeQueryMockId, url: `/p/${storeQueryMockId}` }), + { + overwriteRoutes: true, + }, + ); fetchMock.resetHistory(); jest.clearAllMocks(); }); afterAll(fetchMock.reset); - describe('via /kv/store', () => { + describe('via permalink api', () => { beforeAll(() => { isFeatureEnabledMock = jest .spyOn(uiCore, 'isFeatureEnabled') @@ -124,11 +127,13 @@ describe('ShareSqlLabQuery', () => { }); const button = screen.getByRole('button'); const { id, remoteId, ...expected } = mockQueryEditor; - const storeQuerySpy = jest.spyOn(utils, 'storeQuery'); userEvent.click(button); - expect(storeQuerySpy.mock.calls).toHaveLength(1); - expect(storeQuerySpy).toHaveBeenCalledWith(expected); - storeQuerySpy.mockRestore(); + await waitFor(() => + expect(fetchMock.calls(storeQueryUrl)).toHaveLength(1), + ); + expect( + JSON.parse(fetchMock.calls(storeQueryUrl)[0][1]?.body as string), + ).toEqual(expected); }); it('calls storeQuery() with unsaved changes', async () => { @@ -139,48 +144,13 @@ describe('ShareSqlLabQuery', () => { }); const button = screen.getByRole('button'); const { id, ...expected } = unsavedQueryEditor; - const storeQuerySpy = jest.spyOn(utils, 'storeQuery'); - userEvent.click(button); - expect(storeQuerySpy.mock.calls).toHaveLength(1); - expect(storeQuerySpy).toHaveBeenCalledWith(expected); - storeQuerySpy.mockRestore(); - }); - }); - - describe('via saved query', () => { - beforeAll(() => { - isFeatureEnabledMock = jest - .spyOn(uiCore, 'isFeatureEnabled') - .mockImplementation(() => false); - }); - - afterAll(() => { - isFeatureEnabledMock.mockReset(); - }); - - it('does not call storeQuery() with the query when getCopyUrl() is called and feature is not enabled', async () => { - await act(async () => { - render(, { - wrapper: standardProvider, - }); - }); - const storeQuerySpy = jest.spyOn(utils, 'storeQuery'); - const button = screen.getByRole('button'); userEvent.click(button); - expect(storeQuerySpy.mock.calls).toHaveLength(0); - storeQuerySpy.mockRestore(); - }); - - it('button is disabled and there is a request to save the query', async () => { - const updatedProps = { - queryEditorId: disabled.id, - }; - - render(, { - wrapper: standardProvider, - }); - const button = await screen.findByRole('button', { name: /copy link/i }); - expect(button).toBeDisabled(); + await waitFor(() => + expect(fetchMock.calls(storeQueryUrl)).toHaveLength(1), + ); + expect( + JSON.parse(fetchMock.calls(storeQueryUrl)[0][1]?.body as string), + ).toEqual(expected); }); }); }); diff --git a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx index c1a45eccadf21..988b96b51bd80 100644 --- a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx +++ b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx @@ -17,18 +17,16 @@ * under the License. */ import { - FeatureFlag, styled, t, useTheme, - isFeatureEnabled, getClientErrorObject, + SupersetClient, } from '@superset-ui/core'; import Button from 'src/components/Button'; import Icons from 'src/components/Icons'; import withToasts from 'src/components/MessageToasts/withToasts'; import CopyToClipboard from 'src/components/CopyToClipboard'; -import { storeQuery } from 'src/utils/common'; import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor'; import { LOG_ACTIONS_SQLLAB_COPY_LINK } from 'src/logger/LogUtils'; import useLogAction from 'src/logger/useLogAction'; @@ -54,23 +52,21 @@ const ShareSqlLabQuery = ({ }: ShareSqlLabQueryProps) => { const theme = useTheme(); const logAction = useLogAction({ queryEditorId }); - const { dbId, name, schema, autorun, sql, remoteId, templateParams } = - useQueryEditor(queryEditorId, [ - 'dbId', - 'name', - 'schema', - 'autorun', - 'sql', - 'remoteId', - 'templateParams', - ]); + const { dbId, name, schema, autorun, sql, templateParams } = useQueryEditor( + queryEditorId, + ['dbId', 'name', 'schema', 'autorun', 'sql', 'templateParams'], + ); - const getCopyUrlForKvStore = (callback: Function) => { + const getCopyUrlForPermalink = (callback: Function) => { const sharedQuery = { dbId, name, schema, autorun, sql, templateParams }; - return storeQuery(sharedQuery) - .then(shortUrl => { - callback(shortUrl); + return SupersetClient.post({ + endpoint: '/api/v1/sqllab/permalink', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(sharedQuery), + }) + .then(({ json }) => { + callback(json.url); }) .catch(response => { getClientErrorObject(response).then(() => { @@ -79,61 +75,29 @@ const ShareSqlLabQuery = ({ }); }; - const getCopyUrlForSavedQuery = (callback: Function) => { - let savedQueryToastContent; - - if (remoteId) { - savedQueryToastContent = `${ - window.location.origin + window.location.pathname - }?savedQueryId=${remoteId}`; - callback(savedQueryToastContent); - } else { - savedQueryToastContent = t('Please save the query to enable sharing'); - callback(savedQueryToastContent); - } - }; const getCopyUrl = (callback: Function) => { logAction(LOG_ACTIONS_SQLLAB_COPY_LINK, { shortcut: false, }); - if (isFeatureEnabled(FeatureFlag.ShareQueriesViaKvStore)) { - return getCopyUrlForKvStore(callback); - } - return getCopyUrlForSavedQuery(callback); + return getCopyUrlForPermalink(callback); }; - const buildButton = (canShare: boolean) => { - const tooltip = canShare - ? t('Copy query link to your clipboard') - : t('Save the query to enable this feature'); + const buildButton = () => { + const tooltip = t('Copy query link to your clipboard'); return ( - ); }; - const canShare = - !!remoteId || isFeatureEnabled(FeatureFlag.ShareQueriesViaKvStore); - return ( - <> - {canShare ? ( - - ) : ( - buildButton(canShare) - )} - + ); }; diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx index cab82cd02f5c2..4d1aded5be29f 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx @@ -50,7 +50,7 @@ import type { CursorPosition, } from 'src/SqlLab/types'; import type { DatabaseObject } from 'src/features/databases/types'; -import { debounce, throttle, isBoolean, isEmpty } from 'lodash'; +import { debounce, throttle, isEmpty } from 'lodash'; import Modal from 'src/components/Modal'; import Mousetrap from 'mousetrap'; import Button from 'src/components/Button'; @@ -281,9 +281,10 @@ const SqlEditor: FC = ({ if (unsavedQueryEditor?.id === queryEditor.id) { dbId = unsavedQueryEditor.dbId || dbId; latestQueryId = unsavedQueryEditor.latestQueryId || latestQueryId; - hideLeftBar = isBoolean(unsavedQueryEditor.hideLeftBar) - ? unsavedQueryEditor.hideLeftBar - : hideLeftBar; + hideLeftBar = + typeof unsavedQueryEditor.hideLeftBar === 'boolean' + ? unsavedQueryEditor.hideLeftBar + : hideLeftBar; } return { hasSqlStatement: Boolean(queryEditor.sql?.trim().length > 0), diff --git a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.tsx b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.tsx index f1468fe05a704..09619abd59482 100644 --- a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.tsx +++ b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.tsx @@ -28,10 +28,6 @@ import { Store } from 'redux'; import { RootState } from 'src/views/store'; import { SET_ACTIVE_QUERY_EDITOR } from 'src/SqlLab/actions/sqlLab'; -fetchMock.get('glob:*/api/v1/database/*', {}); -fetchMock.get('glob:*/api/v1/saved_query/*', {}); -fetchMock.get('glob:*/kv/*', {}); - jest.mock('src/SqlLab/components/SqlEditor', () => () => (
)); @@ -46,11 +42,20 @@ const setup = (overridesStore?: Store, initialState?: RootState) => initialState, ...(overridesStore && { store: overridesStore }), }); +let pathStub = jest.spyOn(URI.prototype, 'path'); beforeEach(() => { + fetchMock.get('glob:*/api/v1/database/*', {}); + fetchMock.get('glob:*/api/v1/saved_query/*', {}); + pathStub = jest.spyOn(URI.prototype, 'path').mockReturnValue(`/sqllab/`); store.clearActions(); }); +afterEach(() => { + fetchMock.reset(); + pathStub.mockReset(); +}); + describe('componentDidMount', () => { let uriStub = jest.spyOn(URI.prototype, 'search'); let replaceState = jest.spyOn(window.history, 'replaceState'); @@ -62,7 +67,13 @@ describe('componentDidMount', () => { replaceState.mockReset(); uriStub.mockReset(); }); - test('should handle id', () => { + test('should handle id', async () => { + const id = 1; + fetchMock.get(`glob:*/api/v1/sqllab/permalink/kv:${id}`, { + label: 'test permalink', + sql: 'SELECT * FROM test_table', + dbId: 1, + }); uriStub.mockReturnValue({ id: 1 }); setup(store); expect(replaceState).toHaveBeenCalledWith( @@ -70,6 +81,33 @@ describe('componentDidMount', () => { expect.anything(), '/sqllab', ); + await waitFor(() => + expect( + fetchMock.calls(`glob:*/api/v1/sqllab/permalink/kv:${id}`), + ).toHaveLength(1), + ); + fetchMock.reset(); + }); + test('should handle permalink', async () => { + const key = '9sadkfl'; + fetchMock.get(`glob:*/api/v1/sqllab/permalink/${key}`, { + label: 'test permalink', + sql: 'SELECT * FROM test_table', + dbId: 1, + }); + pathStub.mockReturnValue(`/sqllab/p/${key}`); + setup(store); + await waitFor(() => + expect( + fetchMock.calls(`glob:*/api/v1/sqllab/permalink/${key}`), + ).toHaveLength(1), + ); + expect(replaceState).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + '/sqllab', + ); + fetchMock.reset(); }); test('should handle savedQueryId', () => { uriStub.mockReturnValue({ savedQueryId: 1 }); diff --git a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.tsx b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.tsx index df3099994ec31..3105aa94bda53 100644 --- a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.tsx +++ b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.tsx @@ -77,6 +77,7 @@ class TabbedSqlEditors extends PureComponent { // the reducer. const bootstrapData = getBootstrapData(); const queryParameters = URI(window.location).search(true); + const path = URI(window.location).path(); const { id, name, @@ -96,10 +97,13 @@ class TabbedSqlEditors extends PureComponent { ...bootstrapData.requested_query, ...queryParameters, } as Record; + const permalink = path.match(/\/p\/\w+/)?.[0].slice(3); // Popping a new tab based on the querystring - if (id || sql || savedQueryId || datasourceKey || queryId) { - if (id) { + if (permalink || id || sql || savedQueryId || datasourceKey || queryId) { + if (permalink) { + this.props.actions.popPermalink(permalink); + } else if (id) { this.props.actions.popStoredQuery(id); } else if (savedQueryId) { this.props.actions.popSavedQuery(savedQueryId); @@ -132,7 +136,7 @@ class TabbedSqlEditors extends PureComponent { }; this.props.actions.addQueryEditor(newQueryEditor); } - this.popNewTab(pick(urlParams, Object.keys(queryParameters))); + this.popNewTab(pick(urlParams, Object.keys(queryParameters ?? {}))); } else if (isNewQuery || this.props.queryEditors.length === 0) { this.newQueryEditor(); diff --git a/superset-frontend/src/components/AlteredSliceTag/index.tsx b/superset-frontend/src/components/AlteredSliceTag/index.tsx index bd16d1c287e44..9aca6b46b84df 100644 --- a/superset-frontend/src/components/AlteredSliceTag/index.tsx +++ b/superset-frontend/src/components/AlteredSliceTag/index.tsx @@ -19,9 +19,11 @@ import { useCallback, useEffect, useMemo, useState, FC } from 'react'; import { isEqual, isEmpty } from 'lodash'; -import { QueryFormData, styled, t } from '@superset-ui/core'; +import { QueryFormData, t } from '@superset-ui/core'; import { sanitizeFormData } from 'src/explore/exploreUtils/formData'; import getControlsForVizType from 'src/utils/getControlsForVizType'; +import Label from 'src/components/Label'; +import Icons from 'src/components/Icons'; import { safeStringify } from 'src/utils/safeStringify'; import { Tooltip } from 'src/components/Tooltip'; import ModalTrigger from '../ModalTrigger'; @@ -68,18 +70,6 @@ export type RowType = { control: string; }; -const StyledLabel = styled.span` - ${({ theme }) => ` - font-size: ${theme.typography.sizes.s}px; - color: ${theme.colors.grayscale.dark1}; - background-color: ${theme.colors.alert.base}; - - &:hover { - background-color: ${theme.colors.alert.dark1}; - } - `} -`; - export const alterForComparison = ( value?: string | null | [], ): string | null => { @@ -228,7 +218,14 @@ const AlteredSliceTag: FC = props => { const triggerNode = useMemo( () => ( - {t('Altered')} + ), [], diff --git a/superset-frontend/src/components/AntdThemeProvider/index.tsx b/superset-frontend/src/components/AntdThemeProvider/index.tsx index 03cc8e2c488a3..28ddcabf34ad1 100644 --- a/superset-frontend/src/components/AntdThemeProvider/index.tsx +++ b/superset-frontend/src/components/AntdThemeProvider/index.tsx @@ -20,8 +20,16 @@ import { ConfigProvider, type ConfigProviderProps } from 'antd-v5'; import { getTheme, ThemeType } from 'src/theme/index'; -export const AntdThemeProvider = ({ theme, children }: ConfigProviderProps) => ( - +export const AntdThemeProvider = ({ + theme, + children, + ...rest +}: ConfigProviderProps) => ( + {children} ); diff --git a/superset-frontend/src/components/Button/index.tsx b/superset-frontend/src/components/Button/index.tsx index 8944ef7b81810..c38fbc15cab73 100644 --- a/superset-frontend/src/components/Button/index.tsx +++ b/superset-frontend/src/components/Button/index.tsx @@ -26,10 +26,10 @@ import { import { mix } from 'polished'; import cx from 'classnames'; -import { Button as AntdButton } from 'antd'; +import { Button as AntdButton } from 'antd-v5'; import { useTheme } from '@superset-ui/core'; import { Tooltip, TooltipProps } from 'src/components/Tooltip'; -import { ButtonProps as AntdButtonProps } from 'antd/lib/button'; +import { ButtonProps as AntdButtonProps } from 'antd-v5/lib/button'; export type OnClickHandler = MouseEventHandler; @@ -56,6 +56,25 @@ export type ButtonProps = Omit & showMarginRight?: boolean; }; +const decideType = (buttonStyle: ButtonStyle) => { + const typeMap: Record< + ButtonStyle, + 'primary' | 'default' | 'dashed' | 'link' + > = { + primary: 'primary', + danger: 'primary', + warning: 'primary', + success: 'primary', + secondary: 'default', + default: 'default', + tertiary: 'dashed', + dashed: 'dashed', + link: 'link', + }; + + return typeMap[buttonStyle]; +}; + export default function Button(props: ButtonProps) { const { tooltip, @@ -73,7 +92,7 @@ export default function Button(props: ButtonProps) { const theme = useTheme(); const { colors, transitionTiming, borderRadius, typography } = theme; - const { primary, grayscale, success, warning, error } = colors; + const { primary, grayscale, success, warning } = colors; let height = 32; let padding = 18; @@ -85,25 +104,19 @@ export default function Button(props: ButtonProps) { padding = 10; } - let backgroundColor = primary.light4; - let backgroundColorHover = mix(0.1, primary.base, primary.light4); - let backgroundColorActive = mix(0.25, primary.base, primary.light4); + let backgroundColor; + let backgroundColorHover; + let backgroundColorActive; let backgroundColorDisabled = grayscale.light2; - let color = primary.dark1; - let colorHover = color; + let color; + let colorHover; let borderWidth = 0; let borderStyle = 'none'; - let borderColor = 'transparent'; - let borderColorHover = 'transparent'; + let borderColor; + let borderColorHover; let borderColorDisabled = 'transparent'; - if (buttonStyle === 'primary') { - backgroundColor = primary.base; - backgroundColorHover = primary.dark1; - backgroundColorActive = mix(0.2, grayscale.dark2, primary.dark1); - color = grayscale.light5; - colorHover = color; - } else if (buttonStyle === 'tertiary' || buttonStyle === 'dashed') { + if (buttonStyle === 'tertiary' || buttonStyle === 'dashed') { backgroundColor = grayscale.light5; backgroundColorHover = grayscale.light5; backgroundColorActive = grayscale.light5; @@ -114,10 +127,6 @@ export default function Button(props: ButtonProps) { borderColorHover = primary.light1; borderColorDisabled = grayscale.light2; } else if (buttonStyle === 'danger') { - backgroundColor = error.base; - backgroundColorHover = mix(0.1, grayscale.light5, error.base); - backgroundColorActive = mix(0.2, grayscale.dark2, error.base); - color = grayscale.light5; colorHover = color; } else if (buttonStyle === 'warning') { backgroundColor = warning.base; @@ -135,7 +144,7 @@ export default function Button(props: ButtonProps) { backgroundColor = 'transparent'; backgroundColorHover = 'transparent'; backgroundColorActive = 'transparent'; - colorHover = primary.base; + color = primary.dark1; } const element = children as ReactElement; @@ -149,10 +158,14 @@ export default function Button(props: ButtonProps) { const firstChildMargin = showMarginRight && renderedChildren.length > 1 ? theme.gridUnit * 2 : 0; + const effectiveButtonStyle: ButtonStyle = buttonStyle ?? 'default'; + const button = ( :first-of-type': { + '& > span > :first-of-type': { marginRight: firstChildMargin, }, }} diff --git a/superset-frontend/src/components/Chart/ChartContextMenu/ChartContextMenu.tsx b/superset-frontend/src/components/Chart/ChartContextMenu/ChartContextMenu.tsx index b3b3afbb81e52..9b3752152ea25 100644 --- a/superset-frontend/src/components/Chart/ChartContextMenu/ChartContextMenu.tsx +++ b/superset-frontend/src/components/Chart/ChartContextMenu/ChartContextMenu.tsx @@ -29,6 +29,7 @@ import ReactDOM from 'react-dom'; import { useDispatch, useSelector } from 'react-redux'; import { Behavior, + BinaryQueryObjectFilterClause, ContextMenuFilters, ensureIsArray, FeatureFlag, @@ -47,6 +48,7 @@ import { DrillDetailMenuItems } from '../DrillDetail'; import { getMenuAdjustedY } from '../utils'; import { MenuItemTooltip } from '../DisabledMenuItemTooltip'; import { DrillByMenuItems } from '../DrillBy/DrillByMenuItems'; +import DrillDetailModal from '../DrillDetail/DrillDetailModal'; export enum ContextMenuItem { CrossFilter, @@ -95,6 +97,12 @@ const ChartContextMenu = ( ); const [openKeys, setOpenKeys] = useState([]); + const [modalFilters, setFilters] = useState( + [], + ); + + const [visible, setVisible] = useState(false); + const isDisplayed = (item: ContextMenuItem) => displayedItems === ContextMenuItem.All || ensureIsArray(displayedItems).includes(item); @@ -216,14 +224,13 @@ const ChartContextMenu = ( if (showDrillToDetail) { menuItems.push( , @@ -279,37 +286,58 @@ const ChartContextMenu = ( ); return ReactDOM.createPortal( - { - setOpenKeys(openKeys); - }} - > - {menuItems.length ? ( - menuItems - ) : ( - No actions - )} - - } - trigger={['click']} - onVisibleChange={value => !value && onClose()} - > - + { + setVisible(false); + onClose(); + }} + > + {menuItems.length ? ( + menuItems + ) : ( + {t('No actions')} + )} + + } + trigger={['click']} + onVisibleChange={value => { + setVisible(value); + if (!value) { + setOpenKeys([]); + } }} - /> - , + visible={visible} + > + + + {showDrillToDetail && ( + { + setDrillModalIsOpen(false); + }} + /> + )} + , document.body, ); }; diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.test.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.test.tsx index 2e60c08586fa1..c874da68540a4 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.test.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.test.tsx @@ -70,7 +70,7 @@ const renderMenu = ({ ...rest }: Partial) => render( - + { within(drillByMenuItem).queryByTestId('tooltip-trigger'); expect(tooltipTrigger).not.toBeInTheDocument(); - userEvent.hover( - within(drillByMenuItem).getByRole('button', { name: 'Drill by' }), - ); - expect(await screen.findByTestId('drill-by-submenu')).toBeInTheDocument(); + userEvent.hover(within(drillByMenuItem).getByText('Drill by')); + const drillBySubmenus = await screen.findAllByTestId('drill-by-submenu'); + expect(drillBySubmenus[0]).toBeInTheDocument(); }; getChartMetadataRegistry().registerValue( @@ -176,7 +175,7 @@ test('render menu item with submenu and searchbox', async () => { expect(screen.getByText(column.column_name)).toBeInTheDocument(); }); - const searchbox = screen.getByRole('textbox'); + const searchbox = screen.getAllByPlaceholderText('Search columns')[1]; expect(searchbox).toBeInTheDocument(); userEvent.type(searchbox, 'col1'); diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.test.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.test.tsx index 2299f091b6b16..a57dd675e7366 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.test.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.test.tsx @@ -19,7 +19,7 @@ import { useState } from 'react'; import fetchMock from 'fetch-mock'; -import { omit, isUndefined, omitBy } from 'lodash'; +import { omit, omitBy } from 'lodash'; import userEvent from '@testing-library/user-event'; import { waitFor, within } from '@testing-library/react'; import { render, screen } from 'spec/helpers/testing-library'; @@ -166,7 +166,7 @@ test('should generate Explore url', async () => { form_data: { ...omitBy( omit(formData, ['slice_id', 'slice_name', 'dashboards']), - isUndefined, + i => i === undefined, ), groupby: ['name'], adhoc_filters: [ diff --git a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.test.tsx b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.test.tsx index db3501a169658..002ceb67adbe4 100644 --- a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.test.tsx +++ b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.test.tsx @@ -18,7 +18,7 @@ */ import { useState } from 'react'; import userEvent from '@testing-library/user-event'; -import { render, screen, within } from 'spec/helpers/testing-library'; +import { cleanup, render, screen, within } from 'spec/helpers/testing-library'; import setupPlugins from 'src/setup/setupPlugins'; import { getMockStoreWithNativeFilters } from 'spec/fixtures/mockStore'; import chartQueries, { sliceId } from 'spec/fixtures/mockChartQueries'; @@ -27,6 +27,7 @@ import { Menu } from 'src/components/Menu'; import DrillDetailMenuItems, { DrillDetailMenuItemsProps, } from './DrillDetailMenuItems'; +import DrillDetailModal from './DrillDetailModal'; /* eslint jest/expect-expect: ["warn", { "assertFunctionNames": ["expect*"] }] */ @@ -46,7 +47,7 @@ const { id: defaultChartId, form_data: defaultFormData } = const { slice_name: chartName } = defaultFormData; const unsupportedChartFormData = { ...defaultFormData, - viz_type: VizType.DistBar, + viz_type: VizType.Sankey, }; const noDimensionsFormData = { @@ -74,20 +75,35 @@ const MockRenderChart = ({ formData, isContextMenu, filters, -}: Partial) => { +}: Partial & { chartId?: number }) => { const [showMenu, setShowMenu] = useState(false); + const [modalFilters, setFilters] = useState< + BinaryQueryObjectFilterClause[] | undefined + >(filters); + return ( - - + + + + + { + setShowMenu(false); + }} /> - + ); }; @@ -96,7 +112,7 @@ const renderMenu = ({ formData, isContextMenu, filters, -}: Partial) => { +}: Partial & { chartId?: number }) => { const store = getMockStoreWithNativeFilters(); return render( { + cleanup(); + renderMenu({ + chartId: defaultChartId, + formData: defaultFormData, + isContextMenu: true, + filters, + }); +}; + /** * Drill to Detail modal should appear with correct initial filters */ @@ -124,9 +150,8 @@ const expectDrillToDetailModal = async ( }); expect(modal).toBeInTheDocument(); - expect(screen.getByTestId('modal-filters')).toHaveTextContent( - JSON.stringify(filters), - ); + const modalFilters = await screen.findByTestId('modal-filters'); + expect(modalFilters).toHaveTextContent(JSON.stringify(filters)); }; /** @@ -204,13 +229,11 @@ const expectDrillToDetailByEnabled = async () => { }); await expectMenuItemEnabled(drillToDetailBy); - userEvent.hover( - within(drillToDetailBy).getByRole('button', { name: 'Drill to detail by' }), - ); + userEvent.hover(drillToDetailBy); - expect( - await screen.findByTestId('drill-to-detail-by-submenu'), - ).toBeInTheDocument(); + const submenus = await screen.findAllByTestId('drill-to-detail-by-submenu'); + + expect(submenus.length).toEqual(2); }; /** @@ -230,18 +253,17 @@ const expectDrillToDetailByDisabled = async (tooltipContent?: string) => { const expectDrillToDetailByDimension = async ( filter: BinaryQueryObjectFilterClause, ) => { - userEvent.hover(screen.getByRole('button', { name: 'Drill to detail by' })); - const drillToDetailBySubMenu = await screen.findByTestId( + userEvent.hover(screen.getByRole('menuitem', { name: 'Drill to detail by' })); + const drillToDetailBySubMenus = await screen.findAllByTestId( 'drill-to-detail-by-submenu', ); const menuItemName = `Drill to detail by ${filter.formattedVal}`; - const drillToDetailBySubmenuItem = within(drillToDetailBySubMenu).getByRole( - 'menuitem', - { name: menuItemName }, - ); + const drillToDetailBySubmenuItems = await within( + drillToDetailBySubMenus[1], + ).findAllByRole('menuitem'); - await expectMenuItemEnabled(drillToDetailBySubmenuItem); + await expectMenuItemEnabled(drillToDetailBySubmenuItems[0]); await expectDrillToDetailModal(menuItemName, [filter]); }; @@ -251,16 +273,15 @@ const expectDrillToDetailByDimension = async ( const expectDrillToDetailByAll = async ( filters: BinaryQueryObjectFilterClause[], ) => { - userEvent.hover(screen.getByRole('button', { name: 'Drill to detail by' })); - const drillToDetailBySubMenu = await screen.findByTestId( + userEvent.hover(screen.getByRole('menuitem', { name: 'Drill to detail by' })); + const drillToDetailBySubMenus = await screen.findAllByTestId( 'drill-to-detail-by-submenu', ); const menuItemName = 'Drill to detail by all'; - const drillToDetailBySubmenuItem = within(drillToDetailBySubMenu).getByRole( - 'menuitem', - { name: menuItemName }, - ); + const drillToDetailBySubmenuItem = await within( + drillToDetailBySubMenus[1], + ).findByRole('menuitem', { name: menuItemName }); await expectMenuItemEnabled(drillToDetailBySubmenuItem); await expectDrillToDetailModal(menuItemName, filters); @@ -353,22 +374,26 @@ test('context menu for supported chart, dimensions, 1 filter', async () => { filters, }); - await expectDrillToDetailEnabled(); await expectDrillToDetailByEnabled(); await expectDrillToDetailByDimension(filterA); }); -test('context menu for supported chart, dimensions, 2 filters', async () => { +test('context menu for supported chart, dimensions, filter A', async () => { const filters = [filterA, filterB]; - renderMenu({ - formData: defaultFormData, - isContextMenu: true, - filters, - }); - - await expectDrillToDetailEnabled(); + setupMenu(filters); await expectDrillToDetailByEnabled(); await expectDrillToDetailByDimension(filterA); +}); + +test('context menu for supported chart, dimensions, filter B', async () => { + const filters = [filterA, filterB]; + setupMenu(filters); + await expectDrillToDetailByEnabled(); await expectDrillToDetailByDimension(filterB); +}); + +test('context menu for supported chart, dimensions, all filters', async () => { + const filters = [filterA, filterB]; + setupMenu(filters); await expectDrillToDetailByAll(filters); }); diff --git a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.tsx b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.tsx index 45ea7de96f3a5..e20bc0290f777 100644 --- a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.tsx +++ b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.tsx @@ -17,7 +17,13 @@ * under the License. */ -import { ReactNode, RefObject, useCallback, useMemo, useState } from 'react'; +import { + Dispatch, + ReactNode, + SetStateAction, + useCallback, + useMemo, +} from 'react'; import { isEmpty } from 'lodash'; import { Behavior, @@ -33,7 +39,6 @@ import { import { useSelector } from 'react-redux'; import { Menu } from 'src/components/Menu'; import { RootState } from 'src/dashboard/types'; -import DrillDetailModal from './DrillDetailModal'; import { getSubmenuYOffset } from '../utils'; import { MenuItemTooltip } from '../DisabledMenuItemTooltip'; import { MenuItemWithTruncation } from '../MenuItemWithTruncation'; @@ -90,21 +95,20 @@ const StyledFilter = styled(Filter)` `; export type DrillDetailMenuItemsProps = { - chartId: number; formData: QueryFormData; filters?: BinaryQueryObjectFilterClause[]; + setFilters: Dispatch>; isContextMenu?: boolean; contextMenuY?: number; onSelection?: () => void; onClick?: (event: MouseEvent) => void; submenuIndex?: number; - showModal: boolean; setShowModal: (show: boolean) => void; - drillToDetailMenuRef?: RefObject; + key?: string; + forceSubmenuRender?: boolean; }; const DrillDetailMenuItems = ({ - chartId, formData, filters = [], isContextMenu = false, @@ -112,9 +116,9 @@ const DrillDetailMenuItems = ({ onSelection = () => null, onClick = () => null, submenuIndex = 0, - showModal, + setFilters, setShowModal, - drillToDetailMenuRef, + key, ...props }: DrillDetailMenuItemsProps) => { const drillToDetailDisabled = useSelector( @@ -122,10 +126,6 @@ const DrillDetailMenuItems = ({ datasources[formData.datasource]?.database?.disable_drill_to_detail, ); - const [modalFilters, setFilters] = useState( - [], - ); - const openModal = useCallback( (filters, event) => { onClick(event); @@ -136,10 +136,6 @@ const DrillDetailMenuItems = ({ [onClick, onSelection], ); - const closeModal = useCallback(() => { - setShowModal(false); - }, []); - // Check for Behavior.DRILL_TO_DETAIL to tell if plugin handles the `contextmenu` // event for dimensions. If it doesn't, tell the user that drill to detail by // dimension is not supported. If it does, and the `contextmenu` handler didn't @@ -196,7 +192,6 @@ const DrillDetailMenuItems = ({ {...props} key="drill-to-detail" onClick={openModal.bind(null, [])} - ref={drillToDetailMenuRef} > {DRILL_TO_DETAIL} @@ -213,6 +208,7 @@ const DrillDetailMenuItems = ({ popupOffset={[0, submenuYOffset]} popupClassName="chart-context-submenu" title={DRILL_TO_DETAIL_BY} + key={key} >
{filters.map((filter, i) => ( @@ -246,13 +242,6 @@ const DrillDetailMenuItems = ({ <> {drillToDetailMenuItem} {isContextMenu && drillToDetailByMenuItem} - ); }; diff --git a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailModal.tsx b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailModal.tsx index 1b517154f07c2..fa8f22262cbdf 100644 --- a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailModal.tsx +++ b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailModal.tsx @@ -98,7 +98,7 @@ export default function DrillDetailModal({ const dashboardPageId = useContext(DashboardPageIdContext); const { slice_name: chartName } = useSelector( (state: { sliceEntities: { slices: Record } }) => - state.sliceEntities.slices[chartId], + state.sliceEntities?.slices?.[chartId] || {}, ); const canExplore = useSelector((state: RootState) => findPermission('can_explore', 'Superset', state.user?.roles), diff --git a/superset-frontend/src/components/Chart/MenuItemWithTruncation.tsx b/superset-frontend/src/components/Chart/MenuItemWithTruncation.tsx index 1ab3daf485572..9e8802f128421 100644 --- a/superset-frontend/src/components/Chart/MenuItemWithTruncation.tsx +++ b/superset-frontend/src/components/Chart/MenuItemWithTruncation.tsx @@ -21,12 +21,12 @@ import { ReactNode, CSSProperties } from 'react'; import { css, truncationCSS, useCSSTextTruncation } from '@superset-ui/core'; import { Menu } from 'src/components/Menu'; import { Tooltip } from 'src/components/Tooltip'; -import type { MenuProps } from 'antd/lib/menu'; +import { MenuItemProps } from 'antd-v5'; export type MenuItemWithTruncationProps = { tooltipText: ReactNode; children: ReactNode; - onClick?: MenuProps['onClick']; + onClick?: MenuItemProps['onClick']; style?: CSSProperties; }; @@ -41,6 +41,7 @@ export const MenuItemWithTruncation = ({ diff --git a/superset-frontend/src/components/CopyToClipboard/index.tsx b/superset-frontend/src/components/CopyToClipboard/index.tsx index fdaeb385c53d2..10f2f750049cf 100644 --- a/superset-frontend/src/components/CopyToClipboard/index.tsx +++ b/superset-frontend/src/components/CopyToClipboard/index.tsx @@ -97,7 +97,7 @@ class CopyToClipboard extends Component { style={{ cursor }} title={this.props.tooltipText || ''} trigger={['hover']} - arrowPointAtCenter + arrow={{ pointAtCenter: true }} > {this.getDecoratedCopyNode()} diff --git a/superset-frontend/src/components/DatePicker/DatePicker.stories.tsx b/superset-frontend/src/components/DatePicker/DatePicker.stories.tsx index 69a2b689aebd2..563f48f07f7f6 100644 --- a/superset-frontend/src/components/DatePicker/DatePicker.stories.tsx +++ b/superset-frontend/src/components/DatePicker/DatePicker.stories.tsx @@ -16,7 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import { DatePickerProps, RangePickerProps } from 'antd/lib/date-picker'; +import { DatePickerProps } from 'antd-v5'; +import { RangePickerProps } from 'antd-v5/es/date-picker'; import { DatePicker, RangePicker } from '.'; export default { @@ -24,14 +25,17 @@ export default { component: DatePicker, }; -const commonArgs = { - allowClear: true, +const commonArgs: DatePickerProps = { + allowClear: false, autoFocus: true, - bordered: true, disabled: false, + format: 'YYYY-MM-DD hh:mm a', inputReadOnly: false, + order: true, + picker: 'date', + placement: 'bottomLeft', size: 'middle', - format: 'YYYY-MM-DD hh:mm a', + showNow: true, showTime: { format: 'hh:mm a' }, }; @@ -49,6 +53,25 @@ const interactiveTypes = { }, options: ['large', 'middle', 'small'], }, + placement: { + control: { + type: 'select', + }, + options: ['bottomLeft', 'bottomRight', 'topLeft', 'topRight'], + }, + status: { + control: { + type: 'select', + }, + options: ['error', 'warning'], + }, + + variant: { + control: { + type: 'select', + }, + options: ['outlined', 'borderless', 'filled'], + }, }; export const InteractiveDatePicker = (args: DatePickerProps) => ( @@ -57,9 +80,9 @@ export const InteractiveDatePicker = (args: DatePickerProps) => ( InteractiveDatePicker.args = { ...commonArgs, - picker: 'date', placeholder: 'Placeholder', showToday: true, + showTime: { format: 'hh:mm a', needConfirm: false }, }; InteractiveDatePicker.argTypes = interactiveTypes; @@ -70,9 +93,8 @@ export const InteractiveRangePicker = (args: RangePickerProps) => ( InteractiveRangePicker.args = { ...commonArgs, - allowEmpty: true, - showNow: true, separator: '-', + showTime: { format: 'hh:mm a', needConfirm: false }, }; InteractiveRangePicker.argTypes = interactiveTypes; diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/transformProps.js b/superset-frontend/src/components/DatePicker/DatePicker.test.tsx similarity index 68% rename from superset-frontend/plugins/legacy-plugin-chart-sankey/src/transformProps.js rename to superset-frontend/src/components/DatePicker/DatePicker.test.tsx index b8e9f05b284c3..463c3ace57831 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey/src/transformProps.js +++ b/superset-frontend/src/components/DatePicker/DatePicker.test.tsx @@ -16,18 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import { getLabelFontSize } from './utils'; -export default function transformProps(chartProps) { - const { width, height, formData, queriesData } = chartProps; - const { colorScheme, sliceId } = formData; +import { render } from 'spec/helpers/testing-library'; +import { DatePicker, RangePicker } from '.'; - return { - width, - height, - data: queriesData[0].data, - colorScheme, - fontSize: getLabelFontSize(width), - sliceId, - }; -} +test('should render date picker', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); +}); + +test('should render range picker', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); +}); diff --git a/superset-frontend/src/components/DatePicker/index.tsx b/superset-frontend/src/components/DatePicker/index.tsx index 56c0458c4e349..6538ae9b6a30b 100644 --- a/superset-frontend/src/components/DatePicker/index.tsx +++ b/superset-frontend/src/components/DatePicker/index.tsx @@ -16,13 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { DatePicker as AntdDatePicker } from 'antd'; -import { styled } from '@superset-ui/core'; - -const AntdRangePicker = AntdDatePicker.RangePicker; - -export const RangePicker = styled(AntdRangePicker)` - border-radius: ${({ theme }) => theme.gridUnit}px; -`; +import { DatePicker as AntdDatePicker } from 'antd-v5'; export const DatePicker = AntdDatePicker; +export const { RangePicker } = AntdDatePicker; diff --git a/superset-frontend/src/components/Dropdown/index.tsx b/superset-frontend/src/components/Dropdown/index.tsx index dc56d73b32609..ef81bd42ccd07 100644 --- a/superset-frontend/src/components/Dropdown/index.tsx +++ b/superset-frontend/src/components/Dropdown/index.tsx @@ -17,7 +17,6 @@ * under the License. */ import { - RefObject, ReactElement, ReactNode, FocusEvent, @@ -26,6 +25,11 @@ import { } from 'react'; import { AntdDropdown } from 'src/components'; +// TODO: @geido - Remove these after dropdown is fully migrated to Antd v5 +import { + Dropdown as Antd5Dropdown, + DropDownProps as Antd5DropdownProps, +} from 'antd-v5'; import { DropDownProps } from 'antd/lib/dropdown'; import { styled } from '@superset-ui/core'; import Icons from 'src/components/Icons'; @@ -108,11 +112,7 @@ export const Dropdown = ({ ); -interface ExtendedDropDownProps extends DropDownProps { - ref?: RefObject; -} - -export interface NoAnimationDropdownProps extends ExtendedDropDownProps { +export interface NoAnimationDropdownProps extends Antd5DropdownProps { children: ReactNode; onBlur?: (e: FocusEvent) => void; onKeyDown?: (e: KeyboardEvent) => void; @@ -126,8 +126,8 @@ export const NoAnimationDropdown = (props: NoAnimationDropdownProps) => { }); return ( - + {childrenWithProps} - + ); }; diff --git a/superset-frontend/src/components/DropdownSelectableIcon/index.tsx b/superset-frontend/src/components/DropdownSelectableIcon/index.tsx index 12d23dc242332..8d791929d3719 100644 --- a/superset-frontend/src/components/DropdownSelectableIcon/index.tsx +++ b/superset-frontend/src/components/DropdownSelectableIcon/index.tsx @@ -16,8 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import { styled, useTheme } from '@superset-ui/core'; -import { FC, RefObject, useMemo, ReactNode } from 'react'; +import { addAlpha, styled, useTheme } from '@superset-ui/core'; +import { FC, RefObject, useMemo, ReactNode, useState } from 'react'; import Icons from 'src/components/Icons'; import { DropdownButton } from 'src/components/DropdownButton'; import { DropdownButtonProps } from 'antd/lib/dropdown'; @@ -63,6 +63,12 @@ const StyledDropdownButton = styled(DropdownButton as FC)` const StyledMenu = styled(Menu)` ${({ theme }) => ` + box-shadow: + 0 3px 6px -4px ${addAlpha(theme.colors.grayscale.dark2, 0.12)}, + 0 6px 16px 0 + ${addAlpha(theme.colors.grayscale.dark2, 0.08)}, + 0 9px 28px 8px + ${addAlpha(theme.colors.grayscale.dark2, 0.05)}; .info { font-size: ${theme.typography.sizes.s}px; color: ${theme.colors.grayscale.base}; @@ -98,7 +104,17 @@ const StyleSubmenuItem = styled.div` export default (props: DropDownSelectableProps) => { const theme = useTheme(); + const [visible, setVisible] = useState(false); const { icon, info, menuItems, selectedKeys, onSelect } = props; + + const handleVisibleChange = setVisible; + + const handleMenuSelect: MenuProps['onSelect'] = info => { + if (onSelect) { + onSelect(info); + } + setVisible(false); + }; const menuItem = useMemo( () => (label: string | ReactNode, key: string, divider?: boolean) => ( @@ -119,28 +135,34 @@ export default (props: DropDownSelectableProps) => { const overlayMenu = useMemo( () => ( - + <> {info && (
{info}
)} - {menuItems.map(m => - m.children?.length ? ( - - {m.children.map(s => menuItem(s.label, s.key))} - - ) : ( - menuItem(m.label, m.key, m.divider) - ), - )} -
+ + {menuItems.map(m => + m.children?.length ? ( + + {m.children.map(s => menuItem(s.label, s.key))} + + ) : ( + menuItem(m.label, m.key, m.divider) + ), + )} + + ), - [selectedKeys, onSelect, info, menuItems, menuItem], + [selectedKeys, onSelect, info, menuItems, menuItem, handleMenuSelect], ); return ( @@ -148,6 +170,8 @@ export default (props: DropDownSelectableProps) => { overlay={overlayMenu} trigger={['click']} icon={icon} + visible={visible} + onVisibleChange={handleVisibleChange} /> ); }; diff --git a/superset-frontend/src/components/IconButton/index.tsx b/superset-frontend/src/components/IconButton/index.tsx index 826d70e9f4e81..654e4089f5a9c 100644 --- a/superset-frontend/src/components/IconButton/index.tsx +++ b/superset-frontend/src/components/IconButton/index.tsx @@ -17,8 +17,7 @@ * under the License. */ import { styled } from '@superset-ui/core'; -import Button from 'src/components/Button'; -import { ButtonProps as AntdButtonProps } from 'antd/lib/button'; +import Button, { ButtonProps as AntdButtonProps } from 'src/components/Button'; import Icons from 'src/components/Icons'; import LinesEllipsis from 'react-lines-ellipsis'; diff --git a/superset-frontend/src/components/Icons/Icon.tsx b/superset-frontend/src/components/Icons/Icon.tsx index b2e7dbfdc1169..39e5627235ae8 100644 --- a/superset-frontend/src/components/Icons/Icon.tsx +++ b/superset-frontend/src/components/Icons/Icon.tsx @@ -36,6 +36,10 @@ const AntdIconComponent = ({ export const StyledIcon = styled(AntdIconComponent)` ${({ iconColor }) => iconColor && `color: ${iconColor};`}; + span { + // Fixing alignement on some of the icons + line-height: 0px; + } font-size: ${({ iconSize, theme }) => iconSize ? `${theme.typography.sizes[iconSize] || theme.typography.sizes.m}px` diff --git a/superset-frontend/src/components/Icons/Icons.stories.tsx b/superset-frontend/src/components/Icons/Icons.stories.tsx index 5a21daf5fb7b7..1774176767da5 100644 --- a/superset-frontend/src/components/Icons/Icons.stories.tsx +++ b/superset-frontend/src/components/Icons/Icons.stories.tsx @@ -16,7 +16,9 @@ * specific language governing permissions and limitations * under the License. */ +import { useState } from 'react'; import { styled, supersetTheme } from '@superset-ui/core'; +import { Input } from 'antd-v5'; import Icons from '.'; import IconType from './IconType'; import Icon from './Icon'; @@ -35,8 +37,9 @@ Object.entries(supersetTheme.colors).forEach(([familyName, family]) => { const IconSet = styled.div` display: grid; - grid-template-columns: repeat(auto-fit, 200px); - grid-auto-rows: 100px; + grid-template-columns: repeat(auto-fit, 180px); + grid-auto-rows: 90px; + margin-top: ${({ theme }) => theme.gridUnit * 2}px; `; const IconBlock = styled.div` @@ -49,28 +52,50 @@ const IconBlock = styled.div` margin-top: ${({ theme }) => 2 * theme.gridUnit}px; // Add spacing between icon and name font-size: ${({ theme }) => - theme.typography.sizes.m}; // Optional: adjust font size for elegance + theme.typography.sizes.s}; // Optional: adjust font size for elegance color: ${({ theme }) => theme.colors.grayscale.base}; // Optional: subtle color for the name } `; +const SearchBox = styled(Input.Search)` + margin-bottom: ${({ theme }) => theme.gridUnit * 4}px; + width: 100%; + max-width: 400px; +`; + export const InteractiveIcons = ({ showNames = true, ...rest -}: IconType & { showNames: boolean }) => ( - - {Object.keys(Icons).map(k => { - const IconComponent = Icons[k]; - return ( - - - {showNames && {k}} - - ); - })} - -); +}: IconType & { showNames: boolean }) => { + const [searchTerm, setSearchTerm] = useState(''); + + // Filter icons based on the search term + const filteredIcons = Object.keys(Icons).filter(k => + k.toLowerCase().includes(searchTerm.toLowerCase()), + ); + + return ( +
+ setSearchTerm(e.target.value)} + allowClear + /> + + {filteredIcons.map(k => { + const IconComponent = Icons[k]; + return ( + + + {showNames && {k}} + + ); + })} + +
+ ); +}; InteractiveIcons.argTypes = { showNames: { diff --git a/superset-frontend/src/components/Label/Label.stories.tsx b/superset-frontend/src/components/Label/Label.stories.tsx index 5c46182465f94..20225811d38f3 100644 --- a/superset-frontend/src/components/Label/Label.stories.tsx +++ b/superset-frontend/src/components/Label/Label.stories.tsx @@ -17,14 +17,17 @@ * under the License. */ import { action } from '@storybook/addon-actions'; -import Label, { Type } from './index'; +import { Meta, StoryFn } from '@storybook/react'; +import Label, { Type, DatasetTypeLabel, PublishedLabel } from './index'; +// Define the default export with Storybook configuration export default { title: 'Label', component: Label, - excludeStories: 'options', -}; + excludeStories: ['options'], +} as Meta; +// Explicitly type the options array as an array of `Type` export const options: Type[] = [ 'default', 'alert', @@ -36,39 +39,60 @@ export const options: Type[] = [ 'secondary', ]; -export const LabelGallery = () => ( - <> -

Non-interactive

- {Object.values(options).map((opt: Type) => ( - - ))} -
-

Interactive

- {Object.values(options).map((opt: Type) => ( - - ))} - -); +// Define the props for the `LabelGallery` component +interface LabelGalleryProps { + hasOnClick?: boolean; + monospace?: boolean; +} + +// Use the `StoryFn` type for LabelGallery +export const LabelGallery: StoryFn = ( + props: LabelGalleryProps, +) => { + const onClick = props.hasOnClick ? action('clicked') : undefined; -export const InteractiveLabel = (args: any) => { - const { hasOnClick, label, monospace, ...rest } = args; return ( - + <> +

Non-interactive

+ {options.map((opt: Type) => ( + + ))} +
+

Interactive

+ {options.map((opt: Type) => ( + + ))} +

Reusable Labels

+
DatasetType
+
+ + +
+
PublishedLabel
+ + + ); }; -InteractiveLabel.args = { +// Define default arguments for Storybook +LabelGallery.args = { hasOnClick: true, - label: 'Example', - monospace: true, + monospace: false, +}; + +// Define argument types for Storybook controls +LabelGallery.argTypes = { + monospace: { + name: 'monospace', + control: { type: 'boolean' }, + }, + hasOnClick: { + name: 'hasOnClick', + control: { type: 'boolean' }, + }, }; diff --git a/superset-frontend/src/components/Label/Label.test.tsx b/superset-frontend/src/components/Label/Label.test.tsx index fdf85b9c9336e..ac57250bc77c3 100644 --- a/superset-frontend/src/components/Label/Label.test.tsx +++ b/superset-frontend/src/components/Label/Label.test.tsx @@ -37,7 +37,9 @@ test('works with an onClick handler', () => { // test stories from the storybook! test('renders all the storybook gallery variants', () => { const { container } = render(); + const nonInteractiveLabelCount = 4; + const renderedLabelCount = options.length * 2 + nonInteractiveLabelCount; expect(container.querySelectorAll('.ant-tag')).toHaveLength( - options.length * 2, + renderedLabelCount, ); }); diff --git a/superset-frontend/src/components/Label/index.tsx b/superset-frontend/src/components/Label/index.tsx index d904a445be830..b745084076076 100644 --- a/superset-frontend/src/components/Label/index.tsx +++ b/superset-frontend/src/components/Label/index.tsx @@ -25,6 +25,8 @@ import { import { Tag } from 'src/components'; import { useTheme } from '@superset-ui/core'; +import DatasetTypeLabel from 'src/components/Label/reusable/DatasetTypeLabel'; +import PublishedLabel from 'src/components/Label/reusable/PublishedLabel'; export type OnClickHandler = MouseEventHandler; @@ -47,6 +49,7 @@ export interface LabelProps extends HTMLAttributes { children?: ReactNode; role?: string; monospace?: boolean; + icon?: ReactNode; } export default function Label(props: LabelProps) { @@ -58,6 +61,7 @@ export default function Label(props: LabelProps) { style, onClick, children, + icon, ...rest } = props; const { @@ -71,37 +75,44 @@ export default function Label(props: LabelProps) { info, } = colors; - let backgroundColor = grayscale.light3; - let backgroundColorHover = onClick ? primary.light2 : grayscale.light3; - let borderColor = onClick ? grayscale.light2 : 'transparent'; - let borderColorHover = onClick ? primary.light1 : 'transparent'; - let color = grayscale.dark1; + let baseColor; + if (type === 'primary') { + baseColor = primary; + } else if (type === 'secondary') { + baseColor = secondary; + } else if (type === 'success') { + baseColor = success; + } else if (type === 'alert') { + baseColor = alert; + } else if (type === 'warning') { + baseColor = warning; + } else if (type === 'danger') { + baseColor = error; + } else if (type === 'info') { + baseColor = info; + } else { + baseColor = grayscale; + } + const color = baseColor.dark2; + let borderColor = baseColor.light1; + let backgroundColor = baseColor.light2; + + // TODO - REMOVE IF BLOCK LOGIC WHEN shades are fixed to be aligned in terms of brightness + // currently shades for >=light2 are not aligned for primary, default and secondary + if (['default', 'primary', 'secondary'].includes(type)) { + // @ts-ignore + backgroundColor = baseColor.light4; + borderColor = baseColor.light2; + } - if (type !== 'default') { - color = grayscale.light4; + const backgroundColorHover = onClick ? baseColor.light1 : backgroundColor; + const borderColorHover = onClick ? baseColor.base : borderColor; - let baseColor; - if (type === 'alert') { - color = grayscale.dark1; - baseColor = alert; - } else if (type === 'success') { - baseColor = success; - } else if (type === 'warning') { - baseColor = warning; - } else if (type === 'danger') { - baseColor = error; - } else if (type === 'info') { - baseColor = info; - } else if (type === 'secondary') { - baseColor = secondary; - } else { - baseColor = primary; - } - backgroundColor = baseColor.base; - backgroundColorHover = onClick ? baseColor.dark1 : baseColor.base; - borderColor = onClick ? baseColor.dark1 : 'transparent'; - borderColorHover = onClick ? baseColor.dark2 : 'transparent'; + if (type === 'default') { + // Lighter for default + backgroundColor = grayscale.light3; } + const css = { transition: `background-color ${transitionTiming}s`, whiteSpace: 'nowrap', @@ -109,11 +120,14 @@ export default function Label(props: LabelProps) { overflow: 'hidden', textOverflow: 'ellipsis', backgroundColor, + borderRadius: 8, borderColor, - borderRadius: 21, padding: '0.35em 0.8em', lineHeight: 1, color, + display: 'inline-flex', + verticalAlign: 'middle', + alignItems: 'center', maxWidth: '100%', '&:hover': { backgroundColor: backgroundColorHover, @@ -124,12 +138,12 @@ export default function Label(props: LabelProps) { if (monospace) { css['font-family'] = theme.typography.families.monospace; } - return ( @@ -137,3 +151,4 @@ export default function Label(props: LabelProps) { ); } +export { DatasetTypeLabel, PublishedLabel }; diff --git a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/EventFlow.tsx b/superset-frontend/src/components/Label/reusable/DatasetTypeLabel.tsx similarity index 51% rename from superset-frontend/plugins/legacy-plugin-chart-event-flow/src/EventFlow.tsx rename to superset-frontend/src/components/Label/reusable/DatasetTypeLabel.tsx index 7b3cdd6a8bfab..7379665583c7a 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-event-flow/src/EventFlow.tsx +++ b/superset-frontend/src/components/Label/reusable/DatasetTypeLabel.tsx @@ -16,37 +16,34 @@ * specific language governing permissions and limitations * under the License. */ -import { App as EventFlowApp } from '@data-ui/event-flow'; -import { t, TimeseriesDataRecord } from '@superset-ui/core'; +import Icons from 'src/components/Icons'; +import Label from 'src/components/Label'; +import { t } from '@superset-ui/core'; -export interface EventFlowProps { - data: TimeseriesDataRecord[]; - height: number; - width: number; - initialMinEventCount: number; +// Define the prop types for DatasetTypeLabel +interface DatasetTypeLabelProps { + datasetType: 'physical' | 'virtual'; // Accepts only 'physical' or 'virtual' } -export default function EventFlow({ - data, - initialMinEventCount, - height = 400, - width = 400, -}: EventFlowProps) { - if (data) { - return ( - +const SIZE = 's'; // Define the size as a constant + +const DatasetTypeLabel: React.FC = ({ datasetType }) => { + const label: string = + datasetType === 'physical' ? t('Physical') : t('Virtual'); + const icon = + datasetType === 'physical' ? ( + + ) : ( + ); - } + const labelType: 'primary' | 'secondary' = + datasetType === 'physical' ? 'primary' : 'secondary'; return ( -
-
{t('Sorry, there appears to be no data')}
-
+ ); -} +}; + +export default DatasetTypeLabel; diff --git a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/controlPanel.ts b/superset-frontend/src/components/Label/reusable/PublishedLabel.tsx similarity index 53% rename from superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/controlPanel.ts rename to superset-frontend/src/components/Label/reusable/PublishedLabel.tsx index 74d2b7ff76c65..a73be8609e6ea 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/src/controlPanel.ts +++ b/superset-frontend/src/components/Label/reusable/PublishedLabel.tsx @@ -16,33 +16,33 @@ * specific language governing permissions and limitations * under the License. */ +import Icons from 'src/components/Icons'; +import Label from 'src/components/Label'; import { t } from '@superset-ui/core'; -import { ControlPanelConfig } from '@superset-ui/chart-controls'; -const config: ControlPanelConfig = { - controlPanelSections: [ - { - label: t('Query'), - expanded: true, - controlSetRows: [ - ['groupby'], - ['metric'], - ['adhoc_filters'], - ['row_limit'], - ], - }, - { - label: t('Chart Options'), - expanded: true, - controlSetRows: [['color_scheme']], - }, - ], - controlOverrides: { - groupby: { - label: t('Source / Target'), - description: t('Choose a source and a target'), - }, - }, +// Define props for the PublishedLabel component +interface PublishedLabelProps { + isPublished: boolean; // Whether the item is published + onClick?: () => void; // Optional click handler +} + +const PublishedLabel: React.FC = ({ + isPublished, + onClick, +}) => { + const label = isPublished ? t('Published') : t('Draft'); + const icon = isPublished ? ( + + ) : ( + + ); + const labelType = isPublished ? 'primary' : 'secondary'; + + return ( + + ); }; -export default config; +export default PublishedLabel; diff --git a/superset-frontend/src/components/ListView/Filters/DateRange.tsx b/superset-frontend/src/components/ListView/Filters/DateRange.tsx index 8b9f122034c50..bf03012eedf4a 100644 --- a/superset-frontend/src/components/ListView/Filters/DateRange.tsx +++ b/superset-frontend/src/components/ListView/Filters/DateRange.tsx @@ -24,11 +24,14 @@ import { RefObject, } from 'react'; -// TODO: @msyavuz - Replace with dayjs after migrating datepicker to antd5 -import moment, { Moment } from 'moment'; import { styled, t } from '@superset-ui/core'; import { RangePicker } from 'src/components/DatePicker'; import { FormLabel } from 'src/components/Form'; +import { extendedDayjs } from 'src/utils/dates'; +import { Dayjs } from 'dayjs'; +import Loading from 'src/components/Loading'; +import { AntdThemeProvider } from 'src/components/AntdThemeProvider'; +import { useLocale } from 'src/hooks/useLocale'; import { BaseFilter, FilterHandler } from './Base'; interface DateRangeFilterProps extends BaseFilter { @@ -51,11 +54,13 @@ function DateRangeFilter( ref: RefObject, ) { const [value, setValue] = useState(initialValue ?? null); - const momentValue = useMemo((): [Moment, Moment] | null => { + const dayjsValue = useMemo((): [Dayjs, Dayjs] | null => { if (!value || (Array.isArray(value) && !value.length)) return null; - return [moment(value[0]), moment(value[1])]; + return [extendedDayjs(value[0]), extendedDayjs(value[1])]; }, [value]); + const locale = useLocale(); + useImperativeHandle(ref, () => ({ clearFilter: () => { setValue(null); @@ -63,28 +68,33 @@ function DateRangeFilter( }, })); + if (locale === null) { + return ; + } return ( - - {Header} - { - if (!momentRange) { - setValue(null); - onSubmit([]); - return; - } - const changeValue = [ - momentRange[0]?.valueOf() ?? 0, - momentRange[1]?.valueOf() ?? 0, - ] as ValueState; - setValue(changeValue); - onSubmit(changeValue); - }} - /> - + + + {Header} + { + if (!dayjsRange?.[0]?.valueOf() || !dayjsRange?.[1]?.valueOf()) { + setValue(null); + onSubmit([]); + return; + } + const changeValue = [ + dayjsRange[0]?.valueOf() ?? 0, + dayjsRange[1]?.valueOf() ?? 0, + ] as ValueState; + setValue(changeValue); + onSubmit(changeValue); + }} + /> + + ); } diff --git a/superset-frontend/src/components/ListView/ListView.test.jsx b/superset-frontend/src/components/ListView/ListView.test.jsx index 64869ad67acd8..5658f4069e8ae 100644 --- a/superset-frontend/src/components/ListView/ListView.test.jsx +++ b/superset-frontend/src/components/ListView/ListView.test.jsx @@ -20,6 +20,8 @@ import { styledMount as mount } from 'spec/helpers/theming'; import { act } from 'react-dom/test-utils'; import { QueryParamProvider } from 'use-query-params'; import { supersetTheme, ThemeProvider } from '@superset-ui/core'; +import thunk from 'redux-thunk'; +import configureStore from 'redux-mock-store'; import Button from 'src/components/Button'; import { Empty } from 'src/components/EmptyState/Empty'; @@ -33,6 +35,10 @@ import TableCollection from 'src/components/TableCollection'; import Pagination from 'src/components/Pagination/Wrapper'; import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint'; +import { Provider } from 'react-redux'; + +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); function makeMockLocation(query) { const queryStr = encodeURIComponent(query); @@ -125,12 +131,15 @@ const mockedProps = { const factory = (props = mockedProps) => mount( - - - , + + + + + , { wrappingComponent: ThemeProvider, wrappingComponentProps: { theme: supersetTheme }, + useRedux: true, }, ); diff --git a/superset-frontend/src/components/Menu/Menu.stories.tsx b/superset-frontend/src/components/Menu/Menu.stories.tsx new file mode 100644 index 0000000000000..1ba0109167250 --- /dev/null +++ b/superset-frontend/src/components/Menu/Menu.stories.tsx @@ -0,0 +1,63 @@ +/** + * 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 { Menu, MainNav } from '.'; + +export default { + title: 'Menu', + component: Menu as React.FC, +}; + +export const MainNavigation = (args: any) => ( + + + Dashboards + + + Charts + + + Datasets + + +); + +export const InteractiveMenu = (args: any) => ( + + Dashboards + Charts + Datasets + +); + +InteractiveMenu.args = { + defaultSelectedKeys: ['1'], + inlineCollapsed: false, + mode: 'horizontal', + multiple: false, + selectable: true, +}; + +InteractiveMenu.argTypes = { + mode: { + control: { + type: 'select', + }, + options: ['horizontal', 'vertical', 'inline'], + }, +}; diff --git a/superset-frontend/src/components/Menu/index.tsx b/superset-frontend/src/components/Menu/index.tsx index 35251834ba04c..ff2c1c0b6ec2e 100644 --- a/superset-frontend/src/components/Menu/index.tsx +++ b/superset-frontend/src/components/Menu/index.tsx @@ -16,10 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { styled } from '@superset-ui/core'; +import { addAlpha, styled } from '@superset-ui/core'; import { ReactElement } from 'react'; -import { Menu as AntdMenu } from 'antd'; -import { MenuProps as AntdMenuProps } from 'antd/lib/menu'; +import { Menu as AntdMenu } from 'antd-v5'; +import { MenuProps as AntdMenuProps } from 'antd-v5/es/menu'; export type MenuProps = AntdMenuProps; @@ -29,7 +29,9 @@ export enum MenuItemKeyEnum { SubMenuItem = 'submenu-item', } -export type AntdMenuTypeRef = { current: { props: { parentMenu: AntdMenu } } }; +export type AntdMenuTypeRef = { + current: { props: { parentMenu: typeof AntdMenu } }; +}; export type AntdMenuItemType = ReactElement & { ref: AntdMenuTypeRef; @@ -38,35 +40,22 @@ export type AntdMenuItemType = ReactElement & { export type MenuItemChildType = AntdMenuItemType; -export const isAntdMenuItemRef = ( - ref: AntdMenuTypeRef, -): ref is AntdMenuTypeRef => - (ref as AntdMenuTypeRef)?.current?.props?.parentMenu !== undefined; - -export const isAntdMenuItem = (child: MenuItemChildType) => - child?.type?.displayName === 'Styled(MenuItem)'; - -export const isAntdMenuSubmenu = (child: MenuItemChildType) => - child?.type?.isSubMenu === 1; - -export const isSubMenuOrItemType = (type: string) => - type === MenuItemKeyEnum.SubMenu || type === MenuItemKeyEnum.SubMenuItem; - -const MenuItem = styled(AntdMenu.Item)` - > a { +const StyledMenuItem = styled(AntdMenu.Item)` + a { text-decoration: none; } - - &.ant-menu-item { - height: ${({ theme }) => theme.gridUnit * 8}px; - line-height: ${({ theme }) => theme.gridUnit * 8}px; + &.antd5-menu-item { + div { + display: flex; + align-items: center; + justify-content: space-between; + } a { - border-bottom: none; transition: background-color ${({ theme }) => theme.transitionTiming}s; &:after { content: ''; position: absolute; - bottom: -3px; + bottom: -2px; left: 50%; width: 0; height: 3px; @@ -76,74 +65,74 @@ const MenuItem = styled(AntdMenu.Item)` background-color: ${({ theme }) => theme.colors.primary.base}; } &:focus { - border-bottom: none; - background-color: transparent; @media (max-width: 767px) { background-color: ${({ theme }) => theme.colors.primary.light5}; } } } } +`; - &.ant-menu-item, - &.ant-dropdown-menu-item { - span[role='button'] { - display: inline-block; - width: 100%; +// TODO: @geido - Move this to theme after fully migrating dropdown to Antd5 +const StyledMenu = styled(AntdMenu)` + ${({ theme }) => ` + &.antd5-menu-horizontal { + background-color: inherit; + border-bottom: 1px solid transparent; } - transition-duration: 0s; - } + &.antd5-menu-vertical, + &.ant-dropdown-menu { + box-shadow: + 0 3px 6px -4px ${addAlpha(theme.colors.grayscale.dark2, 0.12)}, + 0 6px 16px 0 + ${addAlpha(theme.colors.grayscale.dark2, 0.08)}, + 0 9px 28px 8px + ${addAlpha(theme.colors.grayscale.dark2, 0.05)}; + } + `} `; const StyledNav = styled(AntdMenu)` - line-height: 51px; - border: none; - - & > .ant-menu-item, - & > .ant-menu-submenu { - vertical-align: inherit; - &:hover { - color: ${({ theme }) => theme.colors.grayscale.dark1}; - } - } - - &:not(.ant-menu-dark) > .ant-menu-submenu, - &:not(.ant-menu-dark) > .ant-menu-item { + display: flex; + align-items: center; + height: 100%; + gap: 0; + &.antd5-menu-horizontal > .antd5-menu-item { + height: 100%; + display: flex; + align-items: center; + margin: 0; + border-bottom: 2px solid transparent; + padding: ${({ theme }) => theme.gridUnit * 2}px + ${({ theme }) => theme.gridUnit * 4}px; &:hover { - border-bottom: none; + background-color: ${({ theme }) => theme.colors.primary.light5}; + border-bottom: 2px solid transparent; + & a:after { + opacity: 1; + width: 100%; + } } } - - &:not(.ant-menu-dark) > .ant-menu-submenu, - &:not(.ant-menu-dark) > .ant-menu-item { - margin: 0px; - } - - & > .ant-menu-item > a { - padding: ${({ theme }) => theme.gridUnit * 4}px; + &.antd5-menu-horizontal > .antd5-menu-item-selected { + box-sizing: border-box; + border-bottom: 2px solid ${({ theme }) => theme.colors.primary.base}; } `; const StyledSubMenu = styled(AntdMenu.SubMenu)` - color: ${({ theme }) => theme.colors.grayscale.dark1}; - border-bottom: none; - .ant-menu-submenu-open, - .ant-menu-submenu-active { - background-color: ${({ theme }) => theme.colors.primary.light5}; - .ant-menu-submenu-title { - color: ${({ theme }) => theme.colors.grayscale.dark1}; - background-color: ${({ theme }) => theme.colors.primary.light5}; - border-bottom: none; - margin: 0; + .antd5-menu-submenu-open, + .antd5-menu-submenu-active { + .antd5-menu-submenu-title { &:after { opacity: 1; width: calc(100% - 1); } } } - .ant-menu-submenu-title { - position: relative; - top: ${({ theme }) => -theme.gridUnit - 3}px; + .antd5-menu-submenu-title { + display: flex; + flex-direction: row-reverse; &:after { content: ''; position: absolute; @@ -154,47 +143,27 @@ const StyledSubMenu = styled(AntdMenu.SubMenu)` opacity: 0; transform: translateX(-50%); transition: all ${({ theme }) => theme.transitionTiming}s; - background-color: ${({ theme }) => theme.colors.primary.base}; } } - .ant-menu-submenu-arrow { - top: 67%; - } - & > .ant-menu-submenu-title { - padding: 0 ${({ theme }) => theme.gridUnit * 6}px 0 - ${({ theme }) => theme.gridUnit * 3}px !important; - span[role='img'] { - position: absolute; - 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; - top: 7px; - } - &:hover { - color: ${({ theme }) => theme.colors.primary.base}; - } + + .ant-dropdown-menu-submenu-arrow:before, + .ant-dropdown-menu-submenu-arrow:after { + content: none !important; } `; -export declare type MenuMode = - | 'vertical' - | 'vertical-left' - | 'vertical-right' - | 'horizontal' - | 'inline'; +export type MenuMode = AntdMenuProps['mode']; +export type MenuItem = Required['items'][number]; -export const Menu = Object.assign(AntdMenu, { - Item: MenuItem, +export const Menu = Object.assign(StyledMenu, { + Item: StyledMenuItem, + SubMenu: StyledSubMenu, + Divider: AntdMenu.Divider, + ItemGroup: AntdMenu.ItemGroup, }); export const MainNav = Object.assign(StyledNav, { - Item: MenuItem, + Item: StyledMenuItem, SubMenu: StyledSubMenu, Divider: AntdMenu.Divider, ItemGroup: AntdMenu.ItemGroup, diff --git a/superset-frontend/src/components/ModalTrigger/index.tsx b/superset-frontend/src/components/ModalTrigger/index.tsx index 556291c4db262..58c19347e626d 100644 --- a/superset-frontend/src/components/ModalTrigger/index.tsx +++ b/superset-frontend/src/components/ModalTrigger/index.tsx @@ -102,9 +102,9 @@ const ModalTrigger = forwardRef( )} {!isButton && ( - +
{triggerNode} - +
)} css` padding: 0; border: 1px solid ${theme.colors.primary.dark2}; - &.ant-btn > span.anticon { + &.antd5-btn > span.anticon { line-height: 0; transition: inherit; } diff --git a/superset-frontend/src/components/PopoverDropdown/index.tsx b/superset-frontend/src/components/PopoverDropdown/index.tsx index 067c0177b79a6..9ad507d5f72a2 100644 --- a/superset-frontend/src/components/PopoverDropdown/index.tsx +++ b/superset-frontend/src/components/PopoverDropdown/index.tsx @@ -46,7 +46,7 @@ interface HandleSelectProps { } const MenuItem = styled(Menu.Item)` - &.ant-menu-item { + &.antd5-menu-item { height: auto; line-height: 1.4; @@ -70,7 +70,7 @@ const MenuItem = styled(Menu.Item)` } } - &.ant-menu-item-selected { + &.antd5-menu-item-selected { color: unset; } `; diff --git a/superset-frontend/src/components/Table/cell-renderers/ActionCell/index.tsx b/superset-frontend/src/components/Table/cell-renderers/ActionCell/index.tsx index fad3feb73cca5..675bffe493bc8 100644 --- a/superset-frontend/src/components/Table/cell-renderers/ActionCell/index.tsx +++ b/superset-frontend/src/components/Table/cell-renderers/ActionCell/index.tsx @@ -19,8 +19,7 @@ import { useState, useEffect } from 'react'; import { styled } from '@superset-ui/core'; import { Dropdown, IconOrientation } from 'src/components/Dropdown'; -import { Menu } from 'src/components/Menu'; -import { MenuProps } from 'antd/lib/menu'; +import { Menu, MenuProps } from 'src/components/Menu'; /** * Props interface for Action Cell Renderer diff --git a/superset-frontend/src/components/Timer/index.tsx b/superset-frontend/src/components/Timer/index.tsx index 4237c9f1edb72..f0ae3cf059ad1 100644 --- a/superset-frontend/src/components/Timer/index.tsx +++ b/superset-frontend/src/components/Timer/index.tsx @@ -19,6 +19,7 @@ import { useEffect, useRef, useState } from 'react'; import { styled } from '@superset-ui/core'; import Label, { Type } from 'src/components/Label'; +import Icons from 'src/components/Icons'; import { now, fDuration } from 'src/utils/dates'; @@ -68,7 +69,7 @@ export default function Timer({ }, [endTime, isRunning, startTime]); return ( - + } type={status} role="timer"> {clockStr} ); diff --git a/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.DaylightSavingTime.test.tsx b/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.DaylightSavingTime.test.tsx index 3424cd73abeaa..4cafd197af1c5 100644 --- a/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.DaylightSavingTime.test.tsx +++ b/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.DaylightSavingTime.test.tsx @@ -57,6 +57,6 @@ test('render timezones in correct order for daylight saving time', async () => { // first option is always current timezone expect(options[0]).toHaveTextContent('GMT -04:00 (Eastern Daylight Time)'); expect(options[1]).toHaveTextContent('GMT -11:00 (Pacific/Midway)'); - expect(options[2]).toHaveTextContent('GMT -10:00 (Hawaii Standard Time)'); - expect(options[3]).toHaveTextContent('GMT -09:30 (Pacific/Marquesas)'); + expect(options[2]).toHaveTextContent('GMT -11:00 (Pacific/Niue)'); + expect(options[3]).toHaveTextContent('GMT -11:00 (Pacific/Pago_Pago)'); }); diff --git a/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx b/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx index 4df8a1e550a71..0f9309b5a8be3 100644 --- a/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx +++ b/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx @@ -91,7 +91,7 @@ test('render timezones in correct order for standard time', async () => { const options = await getSelectOptions(); expect(options[0]).toHaveTextContent('GMT -05:00 (Eastern Standard Time)'); expect(options[1]).toHaveTextContent('GMT -11:00 (Pacific/Midway)'); - expect(options[2]).toHaveTextContent('GMT -10:00 (America/Adak)'); + expect(options[2]).toHaveTextContent('GMT -11:00 (Pacific/Niue)'); }); test('can select a timezone values and returns canonical timezone name', async () => { diff --git a/superset-frontend/src/components/TimezoneSelector/index.tsx b/superset-frontend/src/components/TimezoneSelector/index.tsx index 5a733ae376d3d..0584655b85c16 100644 --- a/superset-frontend/src/components/TimezoneSelector/index.tsx +++ b/superset-frontend/src/components/TimezoneSelector/index.tsx @@ -75,28 +75,32 @@ export default function TimezoneSelector({ ); }; - const dedupedTimezones = new Map(); - // TODO: remove this ts-ignore when typescript is upgraded to 5.1 // @ts-ignore const ALL_ZONES: string[] = Intl.supportedValuesOf('timeZone'); - ALL_ZONES.forEach(zone => { - const offsetKey = getOffsetKey(zone); - if (!dedupedTimezones.has(offsetKey)) { - dedupedTimezones.set(offsetKey, zone); - } - }); - const TIMEZONES: string[] = Array.from(dedupedTimezones.values()); - - const TIMEZONE_OPTIONS = TIMEZONES.map(zone => ({ - label: `GMT ${extendedDayjs + const labels = new Set(); + const TIMEZONE_OPTIONS = ALL_ZONES.map(zone => { + const label = `GMT ${extendedDayjs .tz(currentDate, zone) - .format('Z')} (${getTimezoneName(zone)})`, - value: zone, - offsets: getOffsetKey(zone), - timezoneName: zone, - })); + .format('Z')} (${getTimezoneName(zone)})`; + + if (labels.has(label)) { + return null; // Skip duplicates + } + labels.add(label); + return { + label, + value: zone, + offsets: getOffsetKey(zone), + timezoneName: zone, + }; + }).filter(Boolean) as { + label: string; + value: string; + offsets: string; + timezoneName: string; + }[]; const TIMEZONE_OPTIONS_SORT_COMPARATOR = ( a: (typeof TIMEZONE_OPTIONS)[number], diff --git a/superset-frontend/src/dashboard/actions/dashboardLayout.js b/superset-frontend/src/dashboard/actions/dashboardLayout.js index 8d4b3fa56c944..b771ae202915f 100644 --- a/superset-frontend/src/dashboard/actions/dashboardLayout.js +++ b/superset-frontend/src/dashboard/actions/dashboardLayout.js @@ -267,6 +267,8 @@ export function handleComponentDrop(dropResult) { }; } +export const clearDashboardHistory = () => UndoActionCreators.clearHistory(); + // Undo redo ------------------------------------------------------------------ export function undoLayoutAction() { return (dispatch, getState) => { diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx index e5caed6968642..29dc12a7c89cd 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx @@ -55,6 +55,7 @@ import { import { deleteTopLevelTabs, handleComponentDrop, + clearDashboardHistory, } from 'src/dashboard/actions/dashboardLayout'; import { DASHBOARD_GRID_ID, @@ -105,7 +106,7 @@ const StyledHeader = styled.div` grid-row: 1; position: sticky; top: 0; - z-index: 100; + z-index: 99; max-width: 100vw; .empty-droptarget:before { @@ -675,7 +676,10 @@ const DashboardBuilder = () => { ) } buttonText={canEdit && t('Edit the dashboard')} - buttonAction={() => dispatch(setEditMode(true))} + buttonAction={() => { + dispatch(setEditMode(true)); + dispatch(clearDashboardHistory()); + }} image="dashboard.svg" /> )} diff --git a/superset-frontend/src/dashboard/components/DashboardGrid.jsx b/superset-frontend/src/dashboard/components/DashboardGrid.jsx index 704a9283b3208..6c6d5f853a54c 100644 --- a/superset-frontend/src/dashboard/components/DashboardGrid.jsx +++ b/superset-frontend/src/dashboard/components/DashboardGrid.jsx @@ -56,7 +56,6 @@ const GridContent = styled.div` ${({ theme, editMode }) => css` display: flex; flex-direction: column; - /* gutters between rows */ & > div:not(:last-child):not(.empty-droptarget) { ${!editMode && `margin-bottom: ${theme.gridUnit * 4}px`}; @@ -154,8 +153,12 @@ class DashboardGrid extends PureComponent { })); } - handleResizeStop({ id, widthMultiple: width, heightMultiple: height }) { - this.props.resizeComponent({ id, width, height }); + handleResizeStop(_event, _direction, _elementRef, delta, id) { + this.props.resizeComponent({ + id, + width: delta.width, + height: delta.height, + }); this.setState(() => ({ isResizing: false, diff --git a/superset-frontend/src/dashboard/components/DashboardGrid.test.jsx b/superset-frontend/src/dashboard/components/DashboardGrid.test.jsx index 7f412aa0f4cb3..a81d6f4e9a41f 100644 --- a/superset-frontend/src/dashboard/components/DashboardGrid.test.jsx +++ b/superset-frontend/src/dashboard/components/DashboardGrid.test.jsx @@ -24,8 +24,6 @@ import newComponentFactory from 'src/dashboard/util/newComponentFactory'; import { DASHBOARD_GRID_TYPE } from 'src/dashboard/util/componentTypes'; import { GRID_COLUMN_COUNT } from 'src/dashboard/util/constants'; -const args = { id: 'id', widthMultiple: 1, heightMultiple: 3 }; - jest.mock( 'src/dashboard/containers/DashboardComponent', () => @@ -34,7 +32,9 @@ jest.mock( type="button" data-test="mock-dashboard-component" onClick={() => onResizeStart()} - onBlur={() => onResizeStop(args)} + onBlur={() => + onResizeStop(null, null, null, { width: 1, height: 3 }, 'id') + } > Mock 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 c62a1f74fb861..090f12a2bd079 100644 --- a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx +++ b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx @@ -120,7 +120,9 @@ test('should render', () => { test('should render the Download dropdown button when not in edit mode', () => { const mockedProps = createProps(); setup(mockedProps); - expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument(); + expect( + screen.getByRole('menuitem', { name: 'Download' }), + ).toBeInTheDocument(); }); test('should render the menu items', async () => { @@ -194,7 +196,7 @@ test('should render the "Refresh dashboard" menu item as disabled when loading', isLoading: true, }; setup(loadingProps); - expect(screen.getByText('Refresh dashboard')).toHaveClass( + expect(screen.getByText('Refresh dashboard').parentElement).toHaveClass( 'ant-menu-item-disabled', ); }); diff --git a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.tsx b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.tsx index d5ae7aaee2a00..6bc712f6b8490 100644 --- a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.tsx +++ b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.tsx @@ -195,7 +195,7 @@ export class HeaderActionsDropdown extends PureComponent< {editMode && ( {t('Edit CSS')}
} + triggerNode={
{t('Edit CSS')}
} initialCss={this.state.css} onChange={this.changeCss} addDangerToast={addDangerToast} @@ -222,7 +222,7 @@ export class HeaderActionsDropdown extends PureComponent< colorScheme={colorScheme} onSave={onSave} triggerNode={ - {t('Save as')} +
{t('Save as')}
} canOverwrite={userCanEdit} /> @@ -242,24 +242,20 @@ export class HeaderActionsDropdown extends PureComponent< /> {userCanShare && ( - - - + url={url} + copyMenuItemTitle={t('Copy permalink to clipboard')} + emailMenuItemTitle={t('Share permalink by email')} + emailSubject={emailSubject} + emailBody={emailBody} + addSuccessToast={addSuccessToast} + addDangerToast={addDangerToast} + dashboardId={dashboardId} + dashboardComponentId={dashboardComponentId} + /> )} {!editMode && userCanCurate && ( {t('Set filter mapping')} +
{t('Set filter mapping')}
} />
@@ -316,7 +312,7 @@ export class HeaderActionsDropdown extends PureComponent< onChange={this.changeRefreshInterval} editMode={editMode} refreshIntervalOptions={refreshIntervalOptions} - triggerNode={{t('Set auto-refresh interval')}} + triggerNode={
{t('Set auto-refresh interval')}
} />
diff --git a/superset-frontend/src/dashboard/components/Header/index.jsx b/superset-frontend/src/dashboard/components/Header/index.jsx index f77c680e246a4..5ce86cc52f12c 100644 --- a/superset-frontend/src/dashboard/components/Header/index.jsx +++ b/superset-frontend/src/dashboard/components/Header/index.jsx @@ -66,6 +66,7 @@ import { redoLayoutAction, undoLayoutAction, updateDashboardTitle, + clearDashboardHistory, } from '../../actions/dashboardLayout'; import { fetchCharts, @@ -221,6 +222,7 @@ const Header = () => { addWarningToast, onUndo: undoLayoutAction, onRedo: redoLayoutAction, + clearDashboardHistory, setEditMode, setUnsavedChanges, fetchFaveStar, @@ -651,7 +653,10 @@ const Header = () => { {userCanEdit && ( - - - } - responsive - resizable - resizableConfig={{ - minHeight: theme.gridUnit * 128, - minWidth: theme.gridUnit * 128, - defaultSize: { - width: 'auto', - height: '75vh', - }, - }} - draggable - destroyOnClose - > - {modalBody} - - ))()} - + > + {t('Edit chart')} + + + + } + /> ); }; diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx index af8cbd739b778..25750dcca1121 100644 --- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx @@ -16,9 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import { MouseEvent, Key, useState, useRef, RefObject } from 'react'; - -import { useHistory } from 'react-router-dom'; +import { + MouseEvent, + Key, + KeyboardEvent, + useState, + useRef, + RefObject, +} from 'react'; + +import { RouteComponentProps, useHistory } from 'react-router-dom'; import { extendedDayjs } from 'src/utils/dates'; import { Behavior, @@ -29,6 +36,8 @@ import { styled, t, VizType, + BinaryQueryObjectFilterClause, + QueryFormData, } from '@superset-ui/core'; import { useSelector } from 'react-redux'; import { Menu } from 'src/components/Menu'; @@ -44,11 +53,10 @@ import { ResultsPaneOnDashboard } from 'src/explore/components/DataTablesPane'; import { DrillDetailMenuItems } from 'src/components/Chart/DrillDetail'; import { LOG_ACTIONS_CHART_DOWNLOAD_AS_IMAGE } from 'src/logger/LogUtils'; import { MenuKeys, RootState } from 'src/dashboard/types'; +import DrillDetailModal from 'src/components/Chart/DrillDetail/DrillDetailModal'; import { usePermissions } from 'src/hooks/usePermissions'; import { useCrossFiltersScopingModal } from '../nativeFilters/FilterBar/CrossFilters/ScopingModal/useCrossFiltersScopingModal'; -import { handleDropdownNavigation } from './utils'; import { ViewResultsModalTrigger } from './ViewResultsModalTrigger'; -import { SliceHeaderControlsProps } from './types'; // TODO: replace 3 dots with an icon const VerticalDotsContainer = styled.div` @@ -93,6 +101,52 @@ const VerticalDotsTrigger = () => ( ); +export interface SliceHeaderControlsProps { + slice: { + description: string; + viz_type: string; + slice_name: string; + slice_id: number; + slice_description: string; + datasource: string; + }; + + defaultOpen?: boolean; + componentId: string; + dashboardId: number; + chartStatus: string; + isCached: boolean[]; + cachedDttm: string[] | null; + isExpanded?: boolean; + updatedDttm: number | null; + isFullSize?: boolean; + isDescriptionExpanded?: boolean; + formData: QueryFormData; + exploreUrl: string; + + forceRefresh: (sliceId: number, dashboardId: number) => void; + logExploreChart?: (sliceId: number) => void; + logEvent?: (eventName: string, eventData?: object) => void; + toggleExpandSlice?: (sliceId: number) => void; + exportCSV?: (sliceId: number) => void; + exportPivotCSV?: (sliceId: number) => void; + exportFullCSV?: (sliceId: number) => void; + exportXLSX?: (sliceId: number) => void; + exportFullXLSX?: (sliceId: number) => void; + handleToggleFullSize: () => void; + + addDangerToast: (message: string) => void; + addSuccessToast: (message: string) => void; + + supersetCanExplore?: boolean; + supersetCanShare?: boolean; + supersetCanCSV?: boolean; + + crossFiltersEnabled?: boolean; +} +type SliceHeaderControlsPropsWithRouter = SliceHeaderControlsProps & + RouteComponentProps; + const dropdownIconsStyles = css` &&.anticon > .anticon:first-child { margin-right: 0; @@ -100,9 +154,9 @@ const dropdownIconsStyles = css` } `; -const SliceHeaderControls = (props: SliceHeaderControlsProps) => { - const [dropdownIsOpen, setDropdownIsOpen] = useState(false); - const [tableModalIsOpen, setTableModalIsOpen] = useState(false); +const SliceHeaderControls = ( + props: SliceHeaderControlsPropsWithRouter | SliceHeaderControlsProps, +) => { const [drillModalIsOpen, setDrillModalIsOpen] = useState(false); const [selectedKeys, setSelectedKeys] = useState([]); // setting openKeys undefined falls back to uncontrolled behaviour @@ -113,18 +167,11 @@ const SliceHeaderControls = (props: SliceHeaderControlsProps) => { const history = useHistory(); const queryMenuRef: RefObject = useRef(null); - const menuRef: RefObject = useRef(null); - const copyLinkMenuRef: RefObject = useRef(null); - const shareByEmailMenuRef: RefObject = useRef(null); - const drillToDetailMenuRef: RefObject = useRef(null); - - const toggleDropdown = ({ close }: { close?: boolean } = {}) => { - setDropdownIsOpen(!(close || dropdownIsOpen)); - // clear selected keys - setSelectedKeys([]); - // clear out/deselect submenus - // setOpenKeys([]); - }; + const resultsMenuRef: RefObject = useRef(null); + + const [modalFilters, setFilters] = useState( + [], + ); const canEditCrossFilters = useSelector( @@ -147,10 +194,8 @@ const SliceHeaderControls = (props: SliceHeaderControlsProps) => { domEvent, }: { key: Key; - domEvent: MouseEvent; + domEvent: MouseEvent | KeyboardEvent; }) => { - // close menu - toggleDropdown({ close: true }); switch (key) { case MenuKeys.ForceRefresh: refreshChart(); @@ -222,8 +267,8 @@ const SliceHeaderControls = (props: SliceHeaderControlsProps) => { break; } case MenuKeys.ViewResults: { - if (!tableModalIsOpen) { - setTableModalIsOpen(true); + if (resultsMenuRef.current && !resultsMenuRef.current.showModal) { + resultsMenuRef.current.open(domEvent); } break; } @@ -302,8 +347,9 @@ const SliceHeaderControls = (props: SliceHeaderControlsProps) => { selectable={false} data-test={`slice_${slice.slice_id}-menu`} selectedKeys={selectedKeys} + onSelect={({ selectedKeys: keys }) => setSelectedKeys(keys)} + openKeys={openKeys} id={`slice_${slice.slice_id}-menu`} - ref={menuRef} // submenus must be rendered for handleDropdownNavigation forceSubMenuRender {...openKeysProps} @@ -333,7 +379,10 @@ const SliceHeaderControls = (props: SliceHeaderControlsProps) => { )} {canExplore && ( - + {t('Edit chart')} @@ -352,7 +401,7 @@ const SliceHeaderControls = (props: SliceHeaderControlsProps) => { {t('View query')} +
{t('View query')}
} modalTitle={t('View query')} modalBody={} @@ -370,10 +419,9 @@ const SliceHeaderControls = (props: SliceHeaderControlsProps) => { canExplore={props.supersetCanExplore} exploreUrl={props.exploreUrl} triggerNode={ - {t('View as table')} +
{t('View as table')}
} - setShowModal={setTableModalIsOpen} - showModal={tableModalIsOpen} + modalRef={resultsMenuRef} modalTitle={t('Chart Data: %s', slice.slice_name)} modalBody={ { {isFeatureEnabled(FeatureFlag.DrillToDetail) && canDrillToDetail && ( )} {(slice.description || canExplore) && } {supersetCanShare && ( - setOpenKeys(undefined)} - > - key === MenuKeys.CopyLink || key === MenuKeys.ShareByEmail, - )} - /> - + /> )} {props.supersetCanCSV && ( - setOpenKeys(undefined)} - > + } @@ -495,22 +529,12 @@ const SliceHeaderControls = (props: SliceHeaderControlsProps) => { /> )} menu} overlayStyle={dropdownOverlayStyle} trigger={['click']} placement="bottomRight" - visible={dropdownIsOpen} - onVisibleChange={status => toggleDropdown({ close: !status })} - onKeyDown={e => - handleDropdownNavigation( - e, - dropdownIsOpen, - menu, - toggleDropdown, - setSelectedKeys, - setOpenKeys, - ) - } + autoFocus + forceRender > css` @@ -526,6 +550,16 @@ const SliceHeaderControls = (props: SliceHeaderControlsProps) => { + { + setDrillModalIsOpen(false); + }} + chartId={slice.slice_id} + showModal={drillModalIsOpen} + /> + {canEditCrossFilters && scopingModal} ); diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/utils.ts b/superset-frontend/src/dashboard/components/SliceHeaderControls/utils.ts deleted file mode 100644 index ab2d45076bcd5..0000000000000 --- a/superset-frontend/src/dashboard/components/SliceHeaderControls/utils.ts +++ /dev/null @@ -1,293 +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 { - isAntdMenuItem, - isAntdMenuItemRef, - isAntdMenuSubmenu, - isSubMenuOrItemType, - MenuItemChildType, - MenuItemKeyEnum, -} from 'src/components/Menu'; -import { KeyboardEvent, ReactElement, RefObject } from 'react'; -import { ensureIsArray } from '@superset-ui/core'; - -const ACTION_KEYS = { - enter: 'Enter', - spacebar: 'Spacebar', - space: ' ', -}; - -const NAV_KEYS = { - tab: 'Tab', - escape: 'Escape', - up: 'ArrowUp', - down: 'ArrowDown', -}; - -/** - * A MenuItem can be recognized in the tree by the presence of a ref - * - * @param children - * @param currentKeys - * @returns an array of keys - */ -const extractMenuItemRefs = (child: MenuItemChildType): RefObject[] => { - // check that child has props - const childProps: Record = child?.props; - // loop through each prop - if (childProps) { - const arrayProps = Object.values(childProps); - // check if any is of type ref MenuItem - return arrayProps.filter(ref => isAntdMenuItemRef(ref)); - } - return []; -}; - -/** - * Recursively extracts keys from menu items - * - * @param children - * @param currentKeys - * @returns an array of keys and their refs - * - */ -const extractMenuItemsKeys = ( - children: MenuItemChildType[], - currentKeys?: { key: string; ref?: RefObject }[], -): { key: string; ref?: RefObject }[] => { - const allKeys = currentKeys || []; - const arrayChildren = ensureIsArray(children); - - arrayChildren.forEach((child: MenuItemChildType) => { - const isMenuItem = isAntdMenuItem(child); - const refs = extractMenuItemRefs(child); - // key is immediately available in a standard MenuItem - if (isMenuItem) { - const { key } = child; - if (key) { - allKeys.push({ - key, - }); - } - } - // one or more menu items refs are available - if (refs.length) { - allKeys.push( - ...refs.map(ref => ({ key: ref.current.props.eventKey, ref })), - ); - } - - // continue to extract keys from nested children - if (child?.props?.children) { - const childKeys = extractMenuItemsKeys(child.props.children, allKeys); - allKeys.push(...childKeys); - } - }); - - return allKeys; -}; - -/** - * Generates a map of keys and their types for a MenuItem - * Individual refs can be given to extract keys from nested items - * Refs can be used to control the event handlers of the menu items - * - * @param itemChildren - * @param type - * @returns a map of keys and their types - */ -const extractMenuItemsKeyMap = ( - children: MenuItemChildType, -): Record => { - const keysMap: Record = {}; - const childrenArray = ensureIsArray(children); - - childrenArray.forEach((child: MenuItemChildType) => { - const isMenuItem = isAntdMenuItem(child); - const isSubmenu = isAntdMenuSubmenu(child); - const menuItemsRefs = extractMenuItemRefs(child); - - // key is immediately available in MenuItem or SubMenu - if (isMenuItem || isSubmenu) { - const directKey = child?.key; - if (directKey) { - keysMap[directKey] = {}; - keysMap[directKey].type = isSubmenu - ? MenuItemKeyEnum.SubMenu - : MenuItemKeyEnum.MenuItem; - } - } - - // one or more menu items refs are available - if (menuItemsRefs.length) { - menuItemsRefs.forEach(ref => { - const key = ref.current.props.eventKey; - keysMap[key] = {}; - keysMap[key].type = isSubmenu - ? MenuItemKeyEnum.SubMenu - : MenuItemKeyEnum.MenuItem; - keysMap[key].parent = child.key; - keysMap[key].ref = ref; - }); - } - - // if it has children must check for the presence of menu items - if (child?.props?.children) { - const theChildren = child?.props?.children; - const childKeys = extractMenuItemsKeys(theChildren); - childKeys.forEach(keyMap => { - const k = keyMap.key; - keysMap[k] = {}; - keysMap[k].type = MenuItemKeyEnum.SubMenuItem; - keysMap[k].parent = child.key; - if (keyMap.ref) { - keysMap[k].ref = keyMap.ref; - } - }); - } - }); - - return keysMap; -}; - -/** - * - * Determines the next key to select based on the current key and direction - * - * @param keys - * @param keysMap - * @param currentKeyIndex - * @param direction - * @returns the selected key and the open key - */ -const getNavigationKeys = ( - keys: string[], - keysMap: Record, - currentKeyIndex: number, - direction = 'up', -) => { - const step = direction === 'up' ? -1 : 1; - const skipStep = direction === 'up' ? -2 : 2; - const keysLen = direction === 'up' ? 0 : keys.length; - const mathFn = direction === 'up' ? Math.max : Math.min; - let openKey: string | undefined; - let selectedKey = keys[mathFn(currentKeyIndex + step, keysLen)]; - - // go to first key if current key is the last - if (!selectedKey) { - return { selectedKey: keys[0], openKey: undefined }; - } - - const isSubMenu = keysMap[selectedKey]?.type === MenuItemKeyEnum.SubMenu; - if (isSubMenu) { - // this is a submenu, skip to first submenu item - selectedKey = keys[mathFn(currentKeyIndex + skipStep, keysLen)]; - } - // re-evaulate if current selected key is a submenu or submenu item - if (!isSubMenuOrItemType(keysMap[selectedKey].type)) { - openKey = undefined; - } else { - const parentKey = keysMap[selectedKey].parent; - if (parentKey) { - openKey = parentKey; - } - } - return { selectedKey, openKey }; -}; - -export const handleDropdownNavigation = ( - e: KeyboardEvent, - dropdownIsOpen: boolean, - menu: ReactElement, - toggleDropdown: () => void, - setSelectedKeys: (keys: string[]) => void, - setOpenKeys: (keys: string[]) => void, -) => { - if (e.key === NAV_KEYS.tab && !dropdownIsOpen) { - return; // if tab, continue with system tab navigation - } - const menuProps = menu.props || {}; - const keysMap = extractMenuItemsKeyMap(menuProps.children); - const keys = Object.keys(keysMap); - const { selectedKeys = [] } = menuProps; - const currentKeyIndex = keys.indexOf(selectedKeys[0]); - - switch (e.key) { - // toggle the dropdown on keypress - case ACTION_KEYS.enter: - case ACTION_KEYS.spacebar: - case ACTION_KEYS.space: - if (selectedKeys.length) { - const currentKey = selectedKeys[0]; - const currentKeyConf = keysMap[selectedKeys]; - // when a menu item is selected, then trigger - // the menu item's onClick handler - menuProps.onClick?.({ key: currentKey, domEvent: e }); - // trigger click handle on ref - if (currentKeyConf?.ref) { - const refMenuItemProps = currentKeyConf.ref.current.props; - refMenuItemProps.onClick?.({ - key: currentKey, - domEvent: e, - }); - } - // clear out/deselect keys - setSelectedKeys([]); - // close submenus - setOpenKeys([]); - // put focus back on menu trigger - e.currentTarget.focus(); - } - // if nothing was selected, or after selecting new menu item, - toggleDropdown(); - break; - // select the menu items going down - case NAV_KEYS.down: - case NAV_KEYS.tab && !e.shiftKey: { - const { selectedKey, openKey } = getNavigationKeys( - keys, - keysMap, - currentKeyIndex, - 'down', - ); - setSelectedKeys([selectedKey]); - setOpenKeys(openKey ? [openKey] : []); - break; - } - // select the menu items going up - case NAV_KEYS.up: - case NAV_KEYS.tab && e.shiftKey: { - const { selectedKey, openKey } = getNavigationKeys( - keys, - keysMap, - currentKeyIndex, - 'up', - ); - setSelectedKeys([selectedKey]); - setOpenKeys(openKey ? [openKey] : []); - break; - } - case NAV_KEYS.escape: - // close dropdown menu - toggleDropdown(); - break; - default: - break; - } -}; diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.test.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.test.jsx index 223b98d578dfa..35be0144af769 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Chart.test.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.test.jsx @@ -159,7 +159,7 @@ test('should call exportChart when exportCSV is clicked', async () => { }, ); fireEvent.click(getByRole('button', { name: 'More Options' })); - fireEvent.mouseOver(getByRole('button', { name: 'Download right' })); + fireEvent.mouseOver(getByRole('menuitem', { name: 'Download right' })); const exportAction = await findByText('Export to .CSV'); fireEvent.click(exportAction); expect(stubbedExportCSV).toHaveBeenCalledTimes(1); @@ -187,7 +187,7 @@ test('should call exportChart with row_limit props.maxRows when exportFullCSV is }, ); fireEvent.click(getByRole('button', { name: 'More Options' })); - fireEvent.mouseOver(getByRole('button', { name: 'Download right' })); + fireEvent.mouseOver(getByRole('menuitem', { name: 'Download right' })); const exportAction = await findByText('Export to full .CSV'); fireEvent.click(exportAction); expect(stubbedExportCSV).toHaveBeenCalledTimes(1); @@ -214,7 +214,7 @@ test('should call exportChart when exportXLSX is clicked', async () => { }, ); fireEvent.click(getByRole('button', { name: 'More Options' })); - fireEvent.mouseOver(getByRole('button', { name: 'Download right' })); + fireEvent.mouseOver(getByRole('menuitem', { name: 'Download right' })); const exportAction = await findByText('Export to Excel'); fireEvent.click(exportAction); expect(stubbedExportXLSX).toHaveBeenCalledTimes(1); @@ -241,7 +241,7 @@ test('should call exportChart with row_limit props.maxRows when exportFullXLSX i }, ); fireEvent.click(getByRole('button', { name: 'More Options' })); - fireEvent.mouseOver(getByRole('button', { name: 'Download right' })); + fireEvent.mouseOver(getByRole('menuitem', { name: 'Download right' })); const exportAction = await findByText('Export to full Excel'); fireEvent.click(exportAction); expect(stubbedExportXLSX).toHaveBeenCalledTimes(1); diff --git a/superset-frontend/src/dashboard/components/gridComponents/DynamicComponent.tsx b/superset-frontend/src/dashboard/components/gridComponents/DynamicComponent.tsx index 3b97c277d9c72..66db0fd898d0f 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/DynamicComponent.tsx +++ b/superset-frontend/src/dashboard/components/gridComponents/DynamicComponent.tsx @@ -21,6 +21,7 @@ import { DashboardComponentMetadata, JsonObject, t } from '@superset-ui/core'; import backgroundStyleOptions from 'src/dashboard/util/backgroundStyleOptions'; import cx from 'classnames'; import { shallowEqual, useSelector } from 'react-redux'; +import { ResizeCallback, ResizeStartCallback } from 're-resizable'; import { Draggable } from '../dnd/DragDroppable'; import { COLUMN_TYPE, ROW_TYPE } from '../../util/componentTypes'; import WithPopoverMenu from '../menu/WithPopoverMenu'; @@ -45,9 +46,9 @@ type FilterSummaryType = { editMode: boolean; columnWidth: number; availableColumnCount: number; - onResizeStart: Function; - onResizeStop: Function; - onResize: Function; + onResizeStart: ResizeStartCallback; + onResizeStop: ResizeCallback; + onResize: ResizeCallback; deleteComponent: Function; updateComponents: Function; parentId: number; @@ -140,6 +141,7 @@ const DynamicComponent: FC = ({ > ({ @@ -28,9 +29,14 @@ const createProps = () => ({ }); const renderComponent = () => { - render(, { - useRedux: true, - }); + render( + + + , + { + useRedux: true, + }, + ); }; test('Should render menu items', () => { diff --git a/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/index.tsx b/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/index.tsx index 875537fb8e13b..d9ffaaaedf5a3 100644 --- a/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/index.tsx +++ b/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/index.tsx @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -import { Menu } from 'src/components/Menu'; import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core'; import DownloadScreenshot from './DownloadScreenshot'; import { DownloadScreenshotFormat } from './types'; @@ -44,42 +43,38 @@ const DownloadMenuItems = (props: DownloadMenuItemProps) => { isFeatureEnabled(FeatureFlag.EnableDashboardScreenshotEndpoints) && isFeatureEnabled(FeatureFlag.EnableDashboardDownloadWebDriverScreenshot); - return ( - - {isWebDriverScreenshotEnabled ? ( - <> - - - - ) : ( - <> - - - - )} - + return isWebDriverScreenshotEnabled ? ( + <> + + + + ) : ( + <> + + + ); }; diff --git a/superset-frontend/src/dashboard/components/menu/ShareMenuItems/ShareMenuItems.test.tsx b/superset-frontend/src/dashboard/components/menu/ShareMenuItems/ShareMenuItems.test.tsx index 8420847acab4d..16bbcff9eccc3 100644 --- a/superset-frontend/src/dashboard/components/menu/ShareMenuItems/ShareMenuItems.test.tsx +++ b/superset-frontend/src/dashboard/components/menu/ShareMenuItems/ShareMenuItems.test.tsx @@ -36,6 +36,7 @@ const createProps = () => ({ emailSubject: 'Superset dashboard COVID Vaccine Dashboard', emailBody: 'Check out this dashboard: ', dashboardId: DASHBOARD_ID, + title: 'Test Dashboard', }); const { location } = window; @@ -66,30 +67,30 @@ afterAll((): void => { test('Should render menu items', () => { const props = createProps(); render( - + , { useRedux: true }, ); - expect( - screen.getByRole('menuitem', { name: 'Copy dashboard URL' }), - ).toBeInTheDocument(); - expect( - screen.getByRole('menuitem', { name: 'Share dashboard by email' }), - ).toBeInTheDocument(); - expect( - screen.getByRole('button', { name: 'Copy dashboard URL' }), - ).toBeInTheDocument(); - expect( - screen.getByRole('button', { name: 'Share dashboard by email' }), - ).toBeInTheDocument(); + expect(screen.getByText('Copy dashboard URL')).toBeInTheDocument(); + expect(screen.getByText('Share dashboard by email')).toBeInTheDocument(); }); test('Click on "Copy dashboard URL" and succeed', async () => { spy.mockResolvedValue(undefined); const props = createProps(); render( - + , { useRedux: true }, @@ -101,7 +102,7 @@ test('Click on "Copy dashboard URL" and succeed', async () => { expect(props.addDangerToast).toHaveBeenCalledTimes(0); }); - userEvent.click(screen.getByRole('button', { name: 'Copy dashboard URL' })); + userEvent.click(screen.getByText('Copy dashboard URL')); await waitFor(async () => { expect(spy).toHaveBeenCalledTimes(1); @@ -117,7 +118,12 @@ test('Click on "Copy dashboard URL" and fail', async () => { spy.mockRejectedValue(undefined); const props = createProps(); render( - + , { useRedux: true }, @@ -129,7 +135,7 @@ test('Click on "Copy dashboard URL" and fail', async () => { expect(props.addDangerToast).toHaveBeenCalledTimes(0); }); - userEvent.click(screen.getByRole('button', { name: 'Copy dashboard URL' })); + userEvent.click(screen.getByText('Copy dashboard URL')); await waitFor(async () => { expect(spy).toHaveBeenCalledTimes(1); @@ -146,7 +152,12 @@ test('Click on "Copy dashboard URL" and fail', async () => { test('Click on "Share dashboard by email" and succeed', async () => { const props = createProps(); render( - + , { useRedux: true }, @@ -157,9 +168,7 @@ test('Click on "Share dashboard by email" and succeed', async () => { expect(window.location.href).toBe(''); }); - userEvent.click( - screen.getByRole('button', { name: 'Share dashboard by email' }), - ); + userEvent.click(screen.getByText('Share dashboard by email')); await waitFor(() => { expect(props.addDangerToast).toHaveBeenCalledTimes(0); @@ -177,7 +186,12 @@ test('Click on "Share dashboard by email" and fail', async () => { ); const props = createProps(); render( - + , { useRedux: true }, @@ -188,9 +202,7 @@ test('Click on "Share dashboard by email" and fail', async () => { expect(window.location.href).toBe(''); }); - userEvent.click( - screen.getByRole('button', { name: 'Share dashboard by email' }), - ); + userEvent.click(screen.getByText('Share dashboard by email')); await waitFor(() => { expect(window.location.href).toBe(''); diff --git a/superset-frontend/src/dashboard/components/menu/ShareMenuItems/index.tsx b/superset-frontend/src/dashboard/components/menu/ShareMenuItems/index.tsx index 0d7211ba8ebe1..6c5468da24264 100644 --- a/superset-frontend/src/dashboard/components/menu/ShareMenuItems/index.tsx +++ b/superset-frontend/src/dashboard/components/menu/ShareMenuItems/index.tsx @@ -37,6 +37,9 @@ interface ShareMenuItemProps { copyMenuItemRef?: RefObject; shareByEmailMenuItemRef?: RefObject; selectedKeys?: string[]; + setOpenKeys?: Function; + key?: string; + title: string; } const ShareMenuItems = (props: ShareMenuItemProps) => { @@ -49,10 +52,8 @@ const ShareMenuItems = (props: ShareMenuItemProps) => { addSuccessToast, dashboardId, dashboardComponentId, - copyMenuItemRef, - shareByEmailMenuItemRef, - selectedKeys, - ...rest + key, + title, } = props; const { dataMask, activeTabs } = useSelector( (state: RootState) => ({ @@ -95,28 +96,14 @@ const ShareMenuItems = (props: ShareMenuItemProps) => { } return ( - - e.key === MenuKeys.CopyLink ? onCopyLink() : onShareByEmail() - } - > - -
- {copyMenuItemTitle} -
+ + onCopyLink()}> + {copyMenuItemTitle} - -
- {emailMenuItemTitle} -
+ onShareByEmail()}> + {emailMenuItemTitle} -
+ ); }; export default ShareMenuItems; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ChartsScopingListPanel.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ChartsScopingListPanel.tsx index 119763a22ef3e..ad99071f20477 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ChartsScopingListPanel.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ChartsScopingListPanel.tsx @@ -45,7 +45,7 @@ const AddButtonContainer = styled.div` padding-bottom: 1px; } - .ant-btn > .anticon + span { + .antd5-btn > .anticon + span { margin-left: 0; } `} diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/FilterBarSettings.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/FilterBarSettings.test.tsx index e3a80d21e9f30..2c6196962ec5a 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/FilterBarSettings.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/FilterBarSettings.test.tsx @@ -211,9 +211,6 @@ test('On selection change, send request and update checked value', async () => { expect( within(screen.getAllByRole('menuitem')[2]).getByLabelText('check'), ).toBeInTheDocument(); - expect( - within(screen.getAllByRole('menuitem')[3]).queryByLabelText('check'), - ).not.toBeInTheDocument(); userEvent.click(screen.getByText('Horizontal (Top)')); @@ -221,10 +218,6 @@ test('On selection change, send request and update checked value', async () => { expect( await within(screen.getAllByRole('menuitem')[3]).findByLabelText('check'), ).toBeInTheDocument(); - expect( - within(screen.getAllByRole('menuitem')[2]).queryByLabelText('check'), - ).not.toBeInTheDocument(); - // successful query await waitFor(() => expect(fetchMock.lastCall()?.[1]?.body).toEqual( @@ -236,6 +229,10 @@ test('On selection change, send request and update checked value', async () => { }), ), ); + await waitFor(() => { + const menuitems = screen.getAllByRole('menuitem'); + expect(menuitems.length).toBeGreaterThanOrEqual(3); + }); // 2nd check - checkmark stays after successful query expect( @@ -285,6 +282,11 @@ test('On failed request, restore previous selection', async () => { expect(await screen.findByText('Vertical (Left)')).toBeInTheDocument(); + await waitFor(() => { + const menuitems = screen.getAllByRole('menuitem'); + expect(menuitems.length).toBeGreaterThanOrEqual(3); + }); + // checkmark gets rolled back to the original selection after successful query expect( await within(screen.getAllByRole('menuitem')[2]).findByLabelText('check'), diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigPane.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigPane.test.tsx index b927a51b4bc73..83a64be2f6170 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigPane.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigPane.test.tsx @@ -18,7 +18,13 @@ */ import { dashboardLayout } from 'spec/fixtures/mockDashboardLayout'; import { buildNativeFilter } from 'spec/fixtures/mockNativeFilters'; -import { act, fireEvent, render, screen } from 'spec/helpers/testing-library'; +import { + fireEvent, + render, + screen, + waitFor, +} from 'spec/helpers/testing-library'; +import userEvent from '@testing-library/user-event'; import FilterConfigPane from './FilterConfigurePane'; const scrollMock = jest.fn(); @@ -68,7 +74,7 @@ test('drag and drop', async () => { 'div[draggable=true]', ); // const productFilter = await screen.findByText('NATIVE_FILTER-3'); - await act(async () => { + await waitFor(() => { fireEvent.dragStart(productFilter); fireEvent.dragEnter(countryStateFilter); fireEvent.dragOver(countryStateFilter); @@ -83,15 +89,7 @@ test('remove filter', async () => { defaultRender(); // First trash icon const removeFilterIcon = document.querySelector("[alt='RemoveFilter']")!; - await act(async () => { - fireEvent( - removeFilterIcon, - new MouseEvent('click', { - bubbles: true, - cancelable: true, - }), - ); - }); + userEvent.click(removeFilterIcon); expect(defaultProps.onRemove).toHaveBeenCalledWith('NATIVE_FILTER-1'); }); @@ -99,31 +97,14 @@ test('add filter', async () => { defaultRender(); // First trash icon const addFilterButton = await screen.findByText('Add Filter'); - - await act(async () => { - fireEvent( - addFilterButton, - new MouseEvent('click', { - bubbles: true, - cancelable: true, - }), - ); - }); + userEvent.click(addFilterButton); expect(defaultProps.onAdd).toHaveBeenCalledWith('NATIVE_FILTER'); }); test('add divider', async () => { defaultRender(); const addFilterButton = await screen.findByText('Add Divider'); - await act(async () => { - fireEvent( - addFilterButton, - new MouseEvent('click', { - bubbles: true, - cancelable: true, - }), - ); - }); + userEvent.click(addFilterButton); expect(defaultProps.onAdd).toHaveBeenCalledWith('DIVIDER'); }); @@ -148,19 +129,11 @@ test('filter container should scroll to bottom when adding items', async () => { defaultRender(state, props); const addFilterButton = await screen.findByText('Add Filter'); - // add enough filters to make it scroll in the next expectation. - await act(async () => { - for (let i = 0; i < 3; i += 1) { - fireEvent( - addFilterButton, - new MouseEvent('click', { - bubbles: true, - cancelable: true, - }), - ); - } - }); - const containerElement = screen.getByTestId('filter-title-container'); - expect(containerElement.scroll).toHaveBeenCalled(); + userEvent.click(addFilterButton); + + await waitFor(() => { + const containerElement = screen.getByTestId('filter-title-container'); + expect(containerElement.scroll).toHaveBeenCalled(); + }); }); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx index cca776af15647..d836b8255bf77 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx @@ -82,7 +82,6 @@ describe('FilterScope', () => { it('select tree values with 1 excluded', async () => { render(); fireEvent.click(screen.getByText('Scoping')); - fireEvent.click(screen.getByLabelText('Apply to specific panels')); expect(screen.getByRole('tree')).toBeInTheDocument(); fireEvent.click(getTreeSwitcher(2)); fireEvent.click(screen.getByText('CHART_ID2')); @@ -99,7 +98,6 @@ describe('FilterScope', () => { it('select 1 value only', async () => { render(); fireEvent.click(screen.getByText('Scoping')); - fireEvent.click(screen.getByLabelText('Apply to specific panels')); expect(screen.getByRole('tree')).toBeInTheDocument(); fireEvent.click(getTreeSwitcher(2)); fireEvent.click(screen.getByText('CHART_ID2')); @@ -124,7 +122,6 @@ describe('FilterScope', () => { />, ); fireEvent.click(screen.getByText('Scoping')); - fireEvent.click(screen.getByLabelText('Apply to specific panels')); await waitFor(() => { expect(screen.getByRole('tree')).toBeInTheDocument(); 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 682894acc294c..d175f50bccf88 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,13 +17,11 @@ * under the License. */ -import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { NativeFilterScope, styled, t } from '@superset-ui/core'; -import { Radio } from 'src/components/Radio'; -import { AntdForm, Typography } from 'src/components'; -import { ScopingType } from './types'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { NativeFilterScope, styled } from '@superset-ui/core'; +import { AntdForm } from 'src/components'; import ScopingTree from './ScopingTree'; -import { getDefaultScopeValue, isScopingAll } from './utils'; +import { getDefaultScopeValue } from './utils'; type FilterScopeProps = { pathToFormValue?: string[]; @@ -31,7 +29,6 @@ type FilterScopeProps = { formFilterScope?: NativeFilterScope; forceUpdate: Function; filterScope?: NativeFilterScope; - formScopingType?: ScopingType; chartId?: number; initiallyExcludedCharts?: number[]; }; @@ -51,7 +48,6 @@ const CleanFormItem = styled(AntdForm.Item)` const FilterScope: FC = ({ pathToFormValue = [], - formScopingType, formFilterScope, forceUpdate, filterScope, @@ -63,25 +59,14 @@ const FilterScope: FC = ({ () => filterScope || getDefaultScopeValue(chartId, initiallyExcludedCharts), [chartId, filterScope, initiallyExcludedCharts], ); - const lastSpecificScope = useRef(initialFilterScope); - const initialScopingType = useMemo( - () => - isScopingAll(initialFilterScope, chartId) - ? ScopingType.All - : ScopingType.Specific, - [chartId, initialFilterScope], - ); const [hasScopeBeenModified, setHasScopeBeenModified] = useState(false); const onUpdateFormValues = useCallback( (formValues: any) => { - if (formScopingType === ScopingType.Specific) { - lastSpecificScope.current = formValues.scope; - } updateFormValues(formValues); setHasScopeBeenModified(true); }, - [formScopingType, updateFormValues], + [updateFormValues], ); const updateScopes = useCallback( @@ -98,49 +83,20 @@ const FilterScope: FC = ({ useEffect(() => { const updatedFormValues = { scope: initialFilterScope, - scoping: initialScopingType, }; updateScopes(updatedFormValues); - }, [initialFilterScope, initialScopingType, updateScopes]); + }, [initialFilterScope, updateScopes]); return ( - - { - const scope = - value === ScopingType.All - ? getDefaultScopeValue(chartId) - : lastSpecificScope.current; - updateFormValues({ scope }); - setHasScopeBeenModified(true); - forceUpdate(); - }} - > - {t('Apply to all panels')} - - {t('Apply to specific panels')} - - - - - {(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')} - - {(formScopingType ?? initialScopingType) === ScopingType.Specific && ( - - )} +