diff --git a/.github/workflows/chromatic-master.yml b/.github/workflows/chromatic-master.yml deleted file mode 100644 index efdbfec2f65bb..0000000000000 --- a/.github/workflows/chromatic-master.yml +++ /dev/null @@ -1,72 +0,0 @@ -# .github/workflows/chromatic.yml -# see https://www.chromatic.com/docs/github-actions -# -# 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. -# - -# Workflow name -name: 'Chromatic Storybook Master' - -# Event for the workflow -# Only run if changes were made in superset-frontend folder of repo on merge to Master -on: - # This will trigger when a branch merges to master when the PR has changes in the frontend folder updating the chromatic baseline - push: - branches: - - master - paths: - - "superset-frontend/**" - -# List of jobs -jobs: - config: - runs-on: "ubuntu-latest" - outputs: - has-secrets: ${{ steps.check.outputs.has-secrets }} - steps: - - name: "Check for secrets" - id: check - shell: bash - run: | - if [ -n "${{ (secrets.CHROMATIC_PROJECT_TOKEN != '') || '' }}" ]; then - echo "has-secrets=1" >> "$GITHUB_OUTPUT" - fi - - chromatic-deployment: - needs: config - if: needs.config.outputs.has-secrets - # Operating System - runs-on: ubuntu-latest - # Job steps - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # 👈 Required to retrieve git history - - name: Install dependencies - run: npm ci - working-directory: superset-frontend - # 👇 Build and publish Storybook to Chromatic - - name: Build and publish Storybook to Chromatic - id: chromatic-master - uses: chromaui/action@v1 - # Required options for the Chromatic GitHub Action - with: - # 👇 Location of package.json from root of mono-repo - workingDir: superset-frontend - # 👇 Chromatic projectToken, refer to the manage page to obtain it. - projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - exitZeroOnChanges: true # 👈 Option to prevent the workflow from failing - autoAcceptChanges: true # 👈 Option to accept all changes when merging to master diff --git a/.github/workflows/docker-ephemeral-env.yml b/.github/workflows/docker-ephemeral-env.yml index bc877ae9ae737..41f50acb82d38 100644 --- a/.github/workflows/docker-ephemeral-env.yml +++ b/.github/workflows/docker-ephemeral-env.yml @@ -18,8 +18,6 @@ jobs: shell: bash run: | if [ -n "${{ (secrets.AWS_ACCESS_KEY_ID != '' && - secrets.AWS_ACCESS_KEY_ID != '' && - secrets.AWS_SECRET_ACCESS_KEY != '' && secrets.AWS_SECRET_ACCESS_KEY != '') || '' }}" ]; then echo "has-secrets=1" >> "$GITHUB_OUTPUT" echo "has secrets!" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ff5192163300..365ccbc2818f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ under the License. ## Change Log +- [3.1.2](#312-thu-mar-28-113200-2024--0300) +- [3.1.1](#311-fri-feb-9-214933-2024-0100) - [3.1.0](#310-tue-jan-9-150500-2023--0800) - [3.0.3](#303-fri-dec-8-054009-2023--0800) - [3.0.2](#302-mon-nov-20-073838-2023--0500) @@ -37,6 +39,134 @@ under the License. - [1.4.2](#142-sat-mar-19-000806-2022-0200) - [1.4.1](#141) +### 3.1.2 (Thu Mar 28 11:32:00 2024 -0300) + +**Fixes** + +- [#27706](https://github.com/apache/superset/pull/27706) fix: Select onChange is fired when the same item is selected in single mode (@michael-s-molina) +- [#27744](https://github.com/apache/superset/pull/27744) fix: reduce alert error to warning (@eschutho) +- [#27644](https://github.com/apache/superset/pull/27644) fix: Provide more inclusive error handling for saved queries (@john-bodley) +- [#27646](https://github.com/apache/superset/pull/27646) fix: Leverage actual database for rendering Jinjarized SQL (@john-bodley) +- [#27636](https://github.com/apache/superset/pull/27636) fix(sqllab): unable to remove table (@justinpark) +- [#27022](https://github.com/apache/superset/pull/27022) fix(Chart Annotation modal): Table and Superset annotation options will paginate, exceeding previous max limit 100 (@rtexelm) +- [#27552](https://github.com/apache/superset/pull/27552) fix(AlertReports): defaulting grace period to undefined (@fisjac) +- [#27551](https://github.com/apache/superset/pull/27551) fix(AlertReports): clearing custom_width when disabled (@fisjac) +- [#27601](https://github.com/apache/superset/pull/27601) fix: Persist query params appended to permalink (@kgabryje) +- [#27470](https://github.com/apache/superset/pull/27470) fix(sql_parse): Ensure table extraction handles Jinja templating (@john-bodley) +- [#19595](https://github.com/apache/superset/pull/19595) fix: Volatile datasource ordering in dashboard export (@pnadolny13) +- [#27613](https://github.com/apache/superset/pull/27613) fix(Dashboard): Add editMode conditional for translate3d fix on charts to allow intended Fullscreen (@rtexelm) +- [#27388](https://github.com/apache/superset/pull/27388) fix(utils): fix off-by-one error in how rolling window's min_periods truncates dataframe (@sfirke) +- [#27577](https://github.com/apache/superset/pull/27577) fix: sqlglot SQL Server (@betodealmeida) +- [#27576](https://github.com/apache/superset/pull/27576) fix: bump sqlglot to support materialized CTEs (@betodealmeida) +- [#27567](https://github.com/apache/superset/pull/27567) fix(db_engine_specs): Update convert_dttm to work correctly with CrateDB (@hlcianfagna) +- [#27605](https://github.com/apache/superset/pull/27605) fix: Skips Hive tests that are blocking PRs (@michael-s-molina) +- [#27566](https://github.com/apache/superset/pull/27566) fix: guest queries (@betodealmeida) +- [#27464](https://github.com/apache/superset/pull/27464) fix: pass valid SQL to SM (@betodealmeida) +- [#26748](https://github.com/apache/superset/pull/26748) fix: `improve _extract_tables_from_sql` (@betodealmeida) +- [#27260](https://github.com/apache/superset/pull/27260) fix(alerts/reports): implementing custom_width as an Antd number input (@fisjac) +- [#27487](https://github.com/apache/superset/pull/27487) fix(postprocessing): resample with holes (@villebro) +- [#27484](https://github.com/apache/superset/pull/27484) fix: check if guest user modified query (@betodealmeida) +- [#27471](https://github.com/apache/superset/pull/27471) fix(webpack): remove double-dotted file extensions in webpack config (@rusackas) +- [#27411](https://github.com/apache/superset/pull/27411) fix(dashboard): Only fetch CSS templates for dashboard header menu when in edit mode (@mskelton) +- [#27262](https://github.com/apache/superset/pull/27262) fix(Alerts & Reports): Fixing bug that resets cron value to default when empty (@fisjac) +- [#27315](https://github.com/apache/superset/pull/27315) fix(deps): resolving canvg and html2canvas module not found (@fisjac) +- [#27403](https://github.com/apache/superset/pull/27403) fix: missing shared color in mixed timeseries (@justinpark) +- [#27391](https://github.com/apache/superset/pull/27391) fix(sqllab): Close already removed tab (@justinpark) +- [#27364](https://github.com/apache/superset/pull/27364) fix(API): Updating assets via the API should preserve ownership configuration (@Vitor-Avila) +- [#27395](https://github.com/apache/superset/pull/27395) fix: improve explore REST api validations (@dpgaspar) +- [#26205](https://github.com/apache/superset/pull/26205) fix(docker): Remove race condition when building image (@alekseyolg) +- [#27366](https://github.com/apache/superset/pull/27366) fix: Results section in Explore shows an infinite spinner (@michael-s-molina) +- [#27187](https://github.com/apache/superset/pull/27187) fix: numexpr to fix CVE-2023-39631⁠ (2.8.4 => 2.9.0) (@nigzak) +- [#27360](https://github.com/apache/superset/pull/27360) fix: Heatmap numeric sorting (@michael-s-molina) +- [#27308](https://github.com/apache/superset/pull/27308) fix(dashboard): table chart drag preview overflowing container (@rtexelm) +- [#27295](https://github.com/apache/superset/pull/27295) fix(sqllab): invalid dump sql shown after closing tab (@justinpark) +- [#27285](https://github.com/apache/superset/pull/27285) fix(plugin-chart-echarts): calculate Gauge Chart intervals correctly when min value is set (@goto-loop) +- [#27307](https://github.com/apache/superset/pull/27307) fix: Incorrect data type on import page (@michael-s-molina) +- [#27291](https://github.com/apache/superset/pull/27291) fix: Data zoom with horizontal orientation (@michael-s-molina) +- [#27273](https://github.com/apache/superset/pull/27273) fix: Navigating to an invalid page index in lists (@michael-s-molina) +- [#27271](https://github.com/apache/superset/pull/27271) fix: Inoperable dashboard filter slider when range is <= 1 (@michael-s-molina) +- [#27154](https://github.com/apache/superset/pull/27154) fix(import-datasources): Use "admin" user as default for importing datasources (@ddxv) +- [#27258](https://github.com/apache/superset/pull/27258) fix: Sorting charts/dashboards makes the applied filters ineffective (@michael-s-molina) +- [#27213](https://github.com/apache/superset/pull/27213) fix(trino): bumping trino to fix hudi schema fetching (@rusackas) +- [#27236](https://github.com/apache/superset/pull/27236) fix(reports): fixing unit test (@fisjac) +- [#27217](https://github.com/apache/superset/pull/27217) fix(sqlglot): Address regressions introduced in #26476 (@john-bodley) +- [#27233](https://github.com/apache/superset/pull/27233) fix: bump FAB to 4.4.1 (perf issue) (@dpgaspar) +- [#27167](https://github.com/apache/superset/pull/27167) fix: setting important lower bounds versions on requirements (@dpgaspar) +- [#27215](https://github.com/apache/superset/pull/27215) fix: no limit in SELECT \* for TOP dbs (@betodealmeida) +- [#27191](https://github.com/apache/superset/pull/27191) fix: Failed to execute importScripts on worker-css (@michael-s-molina) +- [#27181](https://github.com/apache/superset/pull/27181) fix(sqllab): typeahead search is broken in db selector (@justinpark) +- [#27161](https://github.com/apache/superset/pull/27161) fix(ci): mypy pre-commit issues (@dpgaspar) +- [#27135](https://github.com/apache/superset/pull/27135) fix: Duplicated toast messages (@michael-s-molina) +- [#27132](https://github.com/apache/superset/pull/27132) fix: Plain error message when visiting a dashboard via permalink without permissions (@michael-s-molina) +- [#22840](https://github.com/apache/superset/pull/22840) fix(pivot-table-v2): Added forgotten translation pivot table v2 (@Always-prog) +- [#27128](https://github.com/apache/superset/pull/27128) fix: RLS modal overflow (@michael-s-molina) +- [#27112](https://github.com/apache/superset/pull/27112) fix: gevent upgrade to 23.9.1 (@dpgaspar) +- [#27124](https://github.com/apache/superset/pull/27124) fix: bump grpcio, urllib3 and paramiko (@dpgaspar) +- [#27113](https://github.com/apache/superset/pull/27113) fix: upgrade cryptography to major 42 (@dpgaspar) +- [#27106](https://github.com/apache/superset/pull/27106) fix: Timeseries Y-axis format with contribution mode (@michael-s-molina) + +**Others** + +- [#27281](https://github.com/apache/superset/pull/27281) chore: bump cryptography minimum to 42.0.4 (@sadpandajoe) +- [#27232](https://github.com/apache/superset/pull/27232) chore: Removes Chromatic workflow and dependencies (@michael-s-molina) +- [#27159](https://github.com/apache/superset/pull/27159) chore: bump FAB to 4.4.0 (@dpgaspar) +- [#27129](https://github.com/apache/superset/pull/27129) chore: lower cryptography min version to 41.0.2 (@sadpandajoe) + +### 3.1.1 (Fri Feb 9 21:49:33 2024 +0100) + +**Fixes** + +- [#27066](https://github.com/apache/superset/pull/27066) fix: Drill by with GLOBAL_ASYNC_QUERIES (@kgabryje) +- [#27039](https://github.com/apache/superset/pull/27039) fix: bump FAB to 4.3.11 (@dpgaspar) +- [#26993](https://github.com/apache/superset/pull/26993) fix: chart import validation (@dpgaspar) +- [#27096](https://github.com/apache/superset/pull/27096) fix(big_number): white-space: nowrap to prevent wrapping (@mistercrunch) +- [#27073](https://github.com/apache/superset/pull/27073) fix(drill): no rows returned (@betodealmeida) +- [#27069](https://github.com/apache/superset/pull/27069) fix: Filters sidebar stretching dashboard height (@kgabryje) +- [#27068](https://github.com/apache/superset/pull/27068) fix: Exclude header controls from dashboard PDF export (@kgabryje) +- [#27041](https://github.com/apache/superset/pull/27041) fix(plugins): missing currency on small number format in table chart (@justinpark) +- [#27023](https://github.com/apache/superset/pull/27023) fix(explore): allow free-form d3 format on custom column formatting (@justinpark) +- [#27019](https://github.com/apache/superset/pull/27019) fix: safer error message in alerts (@betodealmeida) +- [#27015](https://github.com/apache/superset/pull/27015) fix(security manager): Users should not have access to all draft dashboards (@Vitor-Avila) +- [#26965](https://github.com/apache/superset/pull/26965) fix(tags): Improve support for tags with colons (@Vitor-Avila) +- [#26946](https://github.com/apache/superset/pull/26946) fix: column values with NaN (@betodealmeida) +- [#26964](https://github.com/apache/superset/pull/26964) fix(plugin-chart-table): Prevent misalignment of totals and headers when scrollbar is visible (@kgabryje) +- [#26332](https://github.com/apache/superset/pull/26332) fix(embedded+async queries): support async queries to work with embedded guest user (@zephyring) +- [#22849](https://github.com/apache/superset/pull/22849) fix(cache): remove unused webserver config & handle trailing slashes (@Usiel) +- [#26889](https://github.com/apache/superset/pull/26889) fix: Allow exporting saved queries without schema information (@sbernauer) +- [#26887](https://github.com/apache/superset/pull/26887) fix: dashboard import validation (@dpgaspar) +- [#26911](https://github.com/apache/superset/pull/26911) fix: handle CRLF endings causing sqlglot failure (@mapledan) +- [#26906](https://github.com/apache/superset/pull/26906) fix(pinot): typo in the name for epoch_ms_to_dttm (@ege-st) +- [#26817](https://github.com/apache/superset/pull/26817) fix: Bar charts horizontal margin adjustment error (@michael-s-molina) +- [#26749](https://github.com/apache/superset/pull/26749) fix: prevent guest user from modifying metrics (@betodealmeida) +- [#25923](https://github.com/apache/superset/pull/25923) fix(deck.gl Multiple Layer Chart): Add Contour and Heatmap Layer as options (@Mattc1221) +- [#26476](https://github.com/apache/superset/pull/26476) fix(sqlparse): improve table parsing (@betodealmeida) +- [#26922](https://github.com/apache/superset/pull/26922) fix(sqllab): autosync fail on migrated queryEditor (@justinpark) +- [#26814](https://github.com/apache/superset/pull/26814) fix(time-series table): Can't compare from the beginning of the time range (@michael-s-molina) +- [#26701](https://github.com/apache/superset/pull/26701) fix(tags): Filter system tags from the tags list (@Vitor-Avila) +- [#26807](https://github.com/apache/superset/pull/26807) fix: Row limit hardcoded (@michael-s-molina) +- [#26674](https://github.com/apache/superset/pull/26674) fix: helm chart comment on SECRET_KEY (@dpgaspar) +- [#26652](https://github.com/apache/superset/pull/26652) fix(import): only import FORMULA annotations (@mistercrunch) +- [#26314](https://github.com/apache/superset/pull/26314) fix(logging): Filter out undefined columns (@john-bodley) +- [#26461](https://github.com/apache/superset/pull/26461) fix(BigQuery): Support special characters in column/metric names used in ORDER BY (@Vitor-Avila) +- [#26744](https://github.com/apache/superset/pull/26744) fix(db2): Improving support for ibm db2 connections (@Vitor-Avila) +- [#26705](https://github.com/apache/superset/pull/26705) fix(legacy-charts): Show Time Grain control for legacy charts (@Vitor-Avila) +- [#26709](https://github.com/apache/superset/pull/26709) fix: do not use lodash/memoize (@rusackas) +- [#25550](https://github.com/apache/superset/pull/25550) fix: Catch ImportErrors for Google SDKs (@skion) +- [#26645](https://github.com/apache/superset/pull/26645) fix(translation): correct translation errors for Chinese(zh) (@Waterkin) +- [#26638](https://github.com/apache/superset/pull/26638) fix: Avoid 500 if end users write bad SQL (@Khrol) +- [#26644](https://github.com/apache/superset/pull/26644) fix: unnecessary logic on CI ephemeral (@dpgaspar) +- [#26625](https://github.com/apache/superset/pull/26625) fix: create virtual dataset validation (@dpgaspar) +- [#26634](https://github.com/apache/superset/pull/26634) fix: RLS modal styling (@geido) +- [#26469](https://github.com/apache/superset/pull/26469) fix(database): allow filtering by UUID (@betodealmeida) +- [#26412](https://github.com/apache/superset/pull/26412) fix(embedded): Hide dashboard fullscreen option for embedded context (@Vitor-Avila) +- [#26355](https://github.com/apache/superset/pull/26355) fix: Trino - handle table not found in SQLLab (@Khrol) + +**Others** + +- [#26716](https://github.com/apache/superset/pull/26716) build(deps): bump csstype from 2.6.9 to 3.1.3 in /superset-frontend (@dependabot[bot]) +- [#26707](https://github.com/apache/superset/pull/26707) chore(helm): Upgrade default Superset version to 3.1.0 (@dnskr) +- [#26431](https://github.com/apache/superset/pull/26431) chore: bump prophet to 1.1.5 (@villebro) + ### 3.1.0 (Tue Jan 9 15:05:00 2023 -0800) **Database Migrations** diff --git a/Dockerfile b/Dockerfile index b9714d6c69bc6..fc3e66703783e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,9 +61,7 @@ ENV LANG=C.UTF-8 \ SUPERSET_HOME="/app/superset_home" \ SUPERSET_PORT=8088 -RUN --mount=target=/var/lib/apt/lists,type=cache \ - --mount=target=/var/cache/apt,type=cache \ - mkdir -p ${PYTHONPATH} superset/static superset-frontend apache_superset.egg-info requirements \ +RUN mkdir -p ${PYTHONPATH} superset/static superset-frontend apache_superset.egg-info requirements \ && useradd --user-group -d ${SUPERSET_HOME} -m --no-log-init --shell /bin/bash superset \ && apt-get update -qq && apt-get install -yqq --no-install-recommends \ build-essential \ @@ -75,7 +73,8 @@ RUN --mount=target=/var/lib/apt/lists,type=cache \ libecpg-dev \ libldap2-dev \ && touch superset/static/version_info.json \ - && chown -R superset:superset ./* + && chown -R superset:superset ./* \ + && rm -rf /var/lib/apt/lists/* COPY --chown=superset:superset setup.py MANIFEST.in README.md ./ # setup.py uses the version information in package.json @@ -112,9 +111,8 @@ ARG GECKODRIVER_VERSION=v0.33.0 \ USER root -RUN --mount=target=/var/lib/apt/lists,type=cache \ - --mount=target=/var/cache/apt,type=cache \ - apt-get install -yqq --no-install-recommends \ +RUN apt-get update -qq \ + && apt-get install -yqq --no-install-recommends \ libnss3 \ libdbus-glib-1-2 \ libgtk-3-0 \ @@ -127,7 +125,7 @@ RUN --mount=target=/var/lib/apt/lists,type=cache \ # Install Firefox && wget -q https://download-installer.cdn.mozilla.net/pub/firefox/releases/${FIREFOX_VERSION}/linux-x86_64/en-US/firefox-${FIREFOX_VERSION}.tar.bz2 -O - | tar xfj - -C /opt \ && ln -s /opt/firefox/firefox /usr/local/bin/firefox \ - && apt-get autoremove -yqq --purge wget && rm -rf /var/[log,tmp]/* /tmp/* + && apt-get autoremove -yqq --purge wget && rm -rf /var/[log,tmp]/* /tmp/* /var/lib/apt/lists/* # Cache everything for dev purposes... RUN --mount=type=bind,target=./requirements/base.txt,src=./requirements/base.txt \ --mount=type=bind,target=./requirements/docker.txt,src=./requirements/docker.txt \ diff --git a/helm/superset/Chart.yaml b/helm/superset/Chart.yaml index cbca942569349..25ff660e38fc2 100644 --- a/helm/superset/Chart.yaml +++ b/helm/superset/Chart.yaml @@ -15,7 +15,7 @@ # limitations under the License. # apiVersion: v2 -appVersion: "3.0.1" +appVersion: "3.1.0" description: Apache Superset is a modern, enterprise-ready business intelligence web application name: superset icon: https://artifacthub.io/image/68c1d717-0e97-491f-b046-754e46f46922@2x @@ -29,7 +29,7 @@ maintainers: - name: craig-rueda email: craig@craigrueda.com url: https://github.com/craig-rueda -version: 0.11.2 +version: 0.12.1 dependencies: - name: postgresql version: 12.1.6 diff --git a/helm/superset/README.md b/helm/superset/README.md index 1eaf4928c158a..27c1232440296 100644 --- a/helm/superset/README.md +++ b/helm/superset/README.md @@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs # superset -![Version: 0.11.2](https://img.shields.io/badge/Version-0.11.2-informational?style=flat-square) +![Version: 0.12.1](https://img.shields.io/badge/Version-0.12.1-informational?style=flat-square) Apache Superset is a modern, enterprise-ready business intelligence web application diff --git a/helm/superset/values.yaml b/helm/superset/values.yaml index 26d454742055c..15c5f7e214df4 100644 --- a/helm/superset/values.yaml +++ b/helm/superset/values.yaml @@ -165,8 +165,8 @@ configOverrides: {} # # The default user self registration role # AUTH_USER_REGISTRATION_ROLE = "Admin" # secret: | - # # Generate your own secret key for encryption. Use openssl rand -base64 42 to generate a good key - # SECRET_KEY = 'YOUR_OWN_RANDOM_GENERATED_SECRET_KEY' + # # Generate your own secret key for encryption. Use `openssl rand -base64 42` to generate a good key + # SECRET_KEY = 'CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET' # -- Same as above but the values are files configOverridesFiles: {} diff --git a/requirements/base.in b/requirements/base.in index 9d8313823765a..b1c67b936ad0b 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -17,3 +17,5 @@ # under the License. # -e file:. +urllib3>=1.26.18 +numexpr>=2.9.0 diff --git a/requirements/base.txt b/requirements/base.txt index df8f3ea5572fa..98ee56910921c 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,4 @@ -# SHA1:a9dde048f1ee1f00586264d726d0e89f16e56183 +# SHA1:f8f2c882290c71f27b1d9f3263cf0c523cb88ad6 # # This file is autogenerated by pip-compile-multi # To update, run: @@ -72,13 +72,11 @@ colorama==0.4.6 # via # apache-superset # flask-appbuilder -convertdate==2.4.0 - # via holidays cron-descriptor==1.2.24 # via apache-superset croniter==1.0.15 # via apache-superset -cryptography==41.0.2 +cryptography==42.0.4 # via # apache-superset # paramiko @@ -90,7 +88,7 @@ dnspython==2.1.0 # via email-validator email-validator==1.1.3 # via flask-appbuilder -exceptiongroup==1.1.1 +exceptiongroup==1.2.0 # via cattrs flask==2.2.5 # via @@ -106,7 +104,7 @@ flask==2.2.5 # flask-session # flask-sqlalchemy # flask-wtf -flask-appbuilder==4.3.10 +flask-appbuilder==4.4.1 # via apache-superset flask-babel==1.0.0 # via flask-appbuilder @@ -150,9 +148,7 @@ gunicorn==21.2.0 # via apache-superset hashids==1.3.1 # via apache-superset -hijri-converter==2.3.1 - # via holidays -holidays==0.23 +holidays==0.25 # via apache-superset humanize==3.11.0 # via apache-superset @@ -215,8 +211,10 @@ nh3==0.2.11 # via apache-superset numba==0.57.1 # via pandas -numexpr==2.8.4 - # via pandas +numexpr==2.9.0 + # via + # -r requirements/base.in + # pandas numpy==1.23.5 # via # apache-superset @@ -238,8 +236,10 @@ packaging==23.1 # shillelagh pandas[performance]==2.0.3 # via apache-superset -paramiko==2.11.0 - # via sshtunnel +paramiko==3.4.0 + # via + # apache-superset + # sshtunnel parsedatetime==2.6 # via apache-superset pgsanity==0.2.9 @@ -263,8 +263,6 @@ pyjwt==2.4.0 # apache-superset # flask-appbuilder # flask-jwt-extended -pymeeus==0.5.12 - # via convertdate pynacl==1.5.0 # via paramiko pyparsing==3.0.6 @@ -318,7 +316,6 @@ six==1.16.0 # via # click-repl # isodate - # paramiko # prison # python-dateutil # url-normalize @@ -338,6 +335,8 @@ sqlalchemy-utils==0.38.3 # via # apache-superset # flask-appbuilder +sqlglot==23.0.2 + # via apache-superset sqlparse==0.4.4 # via apache-superset sshtunnel==0.4.0 @@ -355,8 +354,9 @@ tzdata==2023.3 # via pandas url-normalize==1.4.3 # via requests-cache -urllib3==1.26.6 +urllib3==1.26.18 # via + # -r requirements/base.in # requests # requests-cache # selenium diff --git a/requirements/development.txt b/requirements/development.txt index a73e3a70c5c27..ca80cd60ede93 100644 --- a/requirements/development.txt +++ b/requirements/development.txt @@ -72,7 +72,7 @@ pexpect==4.8.0 # via ipython pickleshare==0.7.5 # via ipython -pillow==9.5.0 +pillow==10.2.0 # via apache-superset progress==1.6 # via -r requirements/development.in diff --git a/requirements/docker.in b/requirements/docker.in index b7e893f3e9c31..5b65cc68718f3 100644 --- a/requirements/docker.in +++ b/requirements/docker.in @@ -15,6 +15,5 @@ # limitations under the License. # -r base.in --e .[postgres] -gevent +-e .[postgres,gevent] greenlet>=2.0.2 diff --git a/requirements/docker.txt b/requirements/docker.txt index 1c6009b9446df..27c135e04c757 100644 --- a/requirements/docker.txt +++ b/requirements/docker.txt @@ -1,4 +1,4 @@ -# SHA1:439e3ee196ce81f342c935117ba5e0eeee8c385b +# SHA1:f00a57c70a52607d638c19f64f426f887382927e # # This file is autogenerated by pip-compile-multi # To update, run: @@ -10,8 +10,8 @@ # via # -r requirements/base.in # -r requirements/docker.in -gevent==22.10.2 - # via -r requirements/docker.in +gevent==23.9.1 + # via apache-superset psycopg2-binary==2.9.6 # via apache-superset zope-event==4.5.0 diff --git a/requirements/testing.in b/requirements/testing.in index 5b498bb09005d..3245886ec854d 100644 --- a/requirements/testing.in +++ b/requirements/testing.in @@ -20,6 +20,7 @@ docker flask-testing freezegun +grpcio>=1.55.3 openapi-spec-validator parameterized pyfakefs diff --git a/requirements/testing.txt b/requirements/testing.txt index c1f6e55d1293a..fce953f8e4125 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -1,4 +1,4 @@ -# SHA1:95300275481abb1413eb98a5c79fb7cf96814cdd +# SHA1:a37a1037f359c1101162ef43288178fbf00c487d # # This file is autogenerated by pip-compile-multi # To update, run: @@ -24,8 +24,6 @@ db-dtypes==1.1.1 # via pandas-gbq docker==6.1.1 # via -r requirements/testing.in -ephem==4.1.4 - # via lunarcalendar flask-testing==0.8.1 # via -r requirements/testing.in fonttools==4.39.4 @@ -70,12 +68,13 @@ googleapis-common-protos==1.59.0 # via # google-api-core # grpcio-status -grpcio==1.54.0 +grpcio==1.60.1 # via + # -r requirements/testing.in # google-api-core # google-cloud-bigquery # grpcio-status -grpcio-status==1.54.0 +grpcio-status==1.60.1 # via google-api-core iniconfig==2.0.0 # via pytest @@ -83,8 +82,6 @@ jsonschema-spec==0.1.4 # via openapi-spec-validator kiwisolver==1.4.4 # via matplotlib -lunarcalendar==0.0.9 - # via prophet matplotlib==3.7.1 # via prophet oauthlib==3.2.2 @@ -101,7 +98,7 @@ pathable==0.4.3 # via jsonschema-spec playwright==1.37.0 # via apache-superset -prophet==1.1.1 +prophet==1.1.5 # via apache-superset proto-plus==1.22.2 # via @@ -138,8 +135,6 @@ rfc3339-validator==0.1.4 # via openapi-schema-validator rsa==4.9 # via google-auth -setuptools-git==1.2 - # via prophet sqlalchemy-bigquery==1.6.1 # via apache-superset statsd==4.0.1 @@ -148,7 +143,7 @@ tqdm==4.65.0 # via # cmdstanpy # prophet -trino==0.324.0 +trino==0.328.0 # via apache-superset tzlocal==4.3 # via trino diff --git a/setup.py b/setup.py index 563cd32342408..62fffb5b598b2 100644 --- a/setup.py +++ b/setup.py @@ -80,10 +80,10 @@ def get_git_sha() -> str: "colorama", "croniter>=0.3.28", "cron-descriptor", - "cryptography>=41.0.2, <41.1.0", + "cryptography>=42.0.4, <43.0.0", "deprecation>=2.1.0, <2.2.0", "flask>=2.2.5, <3.0.0", - "flask-appbuilder>=4.3.10, <5.0.0", + "flask-appbuilder>=4.4.1, <5.0.0", "flask-caching>=2.1.0, <3", "flask-compress>=1.13, <2.0", "flask-talisman>=1.0.0, <2.0", @@ -95,7 +95,7 @@ def get_git_sha() -> str: "geopy", "gunicorn>=21.2.0, <22.0; sys_platform != 'win32'", "hashids>=1.3.1, <2", - "holidays>=0.23, <0.24", + "holidays>=0.25, <0.26", "humanize", "importlib_metadata", "isodate", @@ -107,6 +107,7 @@ def get_git_sha() -> str: "packaging", "pandas[performance]>=2.0.3, <2.1", "parsedatetime", + "paramiko>=3.4.0", "pgsanity", "polyline>=2.0.0, <3.0", "pyparsing>=3.0.6, <4", @@ -125,6 +126,7 @@ def get_git_sha() -> str: "slack_sdk>=3.19.0, <4", "sqlalchemy>=1.4, <2", "sqlalchemy-utils>=0.38.3, <0.39", + "sqlglot>=23.0.2,<24", "sqlparse>=0.4.4, <0.5", "tabulate>=0.8.9, <0.9", "typing-extensions>=4, <5", @@ -151,9 +153,9 @@ def get_git_sha() -> str: "databricks-sql-connector>=2.0.2, <3", "sqlalchemy-databricks>=0.2.0", ], - "db2": ["ibm-db-sa>=0.3.5, <0.4"], + "db2": ["ibm-db-sa>0.3.8, <=0.4.0"], "dremio": ["sqlalchemy-dremio>=1.1.5, <1.3"], - "drill": ["sqlalchemy-drill==0.1.dev"], + "drill": ["sqlalchemy-drill>=1.1.4, <2"], "druid": ["pydruid>=0.6.5,<0.7"], "duckdb": ["duckdb-engine>=0.9.5, <0.10"], "dynamodb": ["pydynamodb>=0.4.2"], @@ -163,6 +165,7 @@ def get_git_sha() -> str: "excel": ["xlrd>=1.2.0, <1.3"], "firebird": ["sqlalchemy-firebird>=0.7.0, <0.8"], "firebolt": ["firebolt-sqlalchemy>=0.0.1"], + "gevent": ["gevent>=23.9.1"], "gsheets": ["shillelagh[gsheetsapi]>=1.2.10, <2"], "hana": ["hdbcli==2.4.162", "sqlalchemy_hana==0.4.0"], "hive": [ @@ -187,19 +190,19 @@ def get_git_sha() -> str: "playwright": ["playwright>=1.37.0, <2"], "postgres": ["psycopg2-binary==2.9.6"], "presto": ["pyhive[presto]>=0.6.5"], - "trino": ["trino>=0.324.0"], - "prophet": ["prophet==1.1.1"], - "redshift": ["sqlalchemy-redshift>=0.8.1, < 0.9"], - "rockset": ["rockset-sqlalchemy>=0.0.1, <1.0.0"], + "trino": ["trino>=0.328.0"], + "prophet": ["prophet>=1.1.5, <2"], + "redshift": ["sqlalchemy-redshift>=0.8.1, <0.9"], + "rockset": ["rockset-sqlalchemy>=0.0.1, <1"], "shillelagh": [ "shillelagh[datasetteapi,gsheetsapi,socrata,weatherapi]>=1.2.10, <2" ], "snowflake": ["snowflake-sqlalchemy>=1.2.4, <2"], "spark": [ "pyhive[hive]>=0.6.5;python_version<'3.11'", - "pyhive[hive_pure_sasl]>=0.7.0", + "pyhive[hive_pure_sasl]>=0.7", "tableschema", - "thrift>=0.14.1, <1.0.0", + "thrift>=0.14.1, <1", ], "teradata": ["teradatasql>=16.20.0.23"], "thumbnails": ["Pillow>=10.0.1, <11"], 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 44f348edc50f5..3cd1f91b49003 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 @@ -173,6 +173,13 @@ describe('Charts list', () => { orderAlphabetical(); cy.getBySel('styled-card').first().contains('% Rural'); }); + + it('should preserve other filters when sorting', () => { + cy.getBySel('styled-card').should('have.length', 25); + setFilter('Type', 'Big Number'); + setFilter('Sort', 'Least recently modified'); + cy.getBySel('styled-card').should('have.length', 3); + }); }); describe('common actions', () => { 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 7dfb7cd673d7f..917ca104550d2 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 @@ -117,6 +117,13 @@ describe('Dashboards list', () => { orderAlphabetical(); cy.getBySel('styled-card').first().contains('Supported Charts Dashboard'); }); + + it('should preserve other filters when sorting', () => { + cy.getBySel('styled-card').should('have.length', 5); + setFilter('Status', 'Published'); + setFilter('Sort', 'Least recently modified'); + cy.getBySel('styled-card').should('have.length', 3); + }); }); describe('common actions', () => { diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index e308f2aed64e3..0576427e07972 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "superset", - "version": "0.0.0-dev", + "version": "3.1.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "superset", - "version": "0.0.0-dev", + "version": "3.1.1", "license": "Apache-2.0", "workspaces": [ "packages/*", @@ -221,7 +221,6 @@ "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-jsx-remove-data-test-id": "^2.1.3", "babel-plugin-lodash": "^3.3.4", - "chromatic": "^6.7.4", "copy-webpack-plugin": "^9.1.0", "cross-env": "^5.2.1", "css-loader": "^6.8.1", @@ -5346,6 +5345,11 @@ "csstype": "^2.5.7" } }, + "node_modules/@emotion/serialize/node_modules/csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" + }, "node_modules/@emotion/sheet": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz", @@ -19616,8 +19620,7 @@ "node_modules/@types/raf": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.2.tgz", - "integrity": "sha512-sM4HyDVlDFl4goOXPF+g9nNHJFZQGot+HgySjM4cRjqXzjdatcEvYrtG4Ia8XumR9T6k8G2tW9B7hnUj51Uf0A==", - "optional": true + "integrity": "sha512-sM4HyDVlDFl4goOXPF+g9nNHJFZQGot+HgySjM4cRjqXzjdatcEvYrtG4Ia8XumR9T6k8G2tW9B7hnUj51Uf0A==" }, "node_modules/@types/range-parser": { "version": "1.2.4", @@ -19773,6 +19776,11 @@ "@types/react": "*" } }, + "node_modules/@types/react/node_modules/csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" + }, "node_modules/@types/redux-localstorage": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/redux-localstorage/-/redux-localstorage-1.0.8.tgz", @@ -23894,7 +23902,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", - "optional": true, "engines": { "node": ">= 0.6.0" } @@ -25040,7 +25047,6 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", - "optional": true, "dependencies": { "@babel/runtime": "^7.12.5", "@types/raf": "^3.4.0", @@ -25312,21 +25318,6 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, - "node_modules/chromatic": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-6.7.4.tgz", - "integrity": "sha512-QW4i8RQsON0JVnFnRf+8y70aIJptvC0Oi/26YJ669Dl03WmJRpobNO5qWFPTiv3KFKMc1Qf6/qFsRVZCtn+bfA==", - "dev": true, - "dependencies": { - "@discoveryjs/json-ext": "^0.5.7", - "@types/webpack-env": "^1.17.0" - }, - "bin": { - "chroma": "bin/main.cjs", - "chromatic": "bin/main.cjs", - "chromatic-cli": "bin/main.cjs" - } - }, "node_modules/chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", @@ -27032,7 +27023,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", - "optional": true, "dependencies": { "utrie": "^1.0.2" } @@ -28067,11 +28057,6 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", "dev": true }, - "node_modules/csstype": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz", - "integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==" - }, "node_modules/currencyformatter.js": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/currencyformatter.js/-/currencyformatter.js-1.0.5.tgz", @@ -35241,7 +35226,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", - "optional": true, "dependencies": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" @@ -40313,13 +40297,13 @@ "@babel/runtime": "^7.14.0", "atob": "^2.1.2", "btoa": "^1.2.1", - "fflate": "^0.4.8" + "canvg": "^3.0.6", + "fflate": "^0.4.8", + "html2canvas": "^1.0.0-rc.5" }, "optionalDependencies": { - "canvg": "^3.0.6", "core-js": "^3.6.0", - "dompurify": "^2.2.0", - "html2canvas": "^1.0.0-rc.5" + "dompurify": "^2.2.0" } }, "node_modules/jsprim": { @@ -52126,6 +52110,11 @@ "@emotion/weak-memoize": "0.2.5" } }, + "node_modules/react-select/node_modules/csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" + }, "node_modules/react-select/node_modules/dom-helpers": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz", @@ -54157,7 +54146,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", - "optional": true, "engines": { "node": ">= 0.8.15" } @@ -55559,7 +55547,6 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.6.0.tgz", "integrity": "sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==", - "optional": true, "engines": { "node": ">=0.1.14" } @@ -56283,7 +56270,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", - "optional": true, "engines": { "node": ">=12.0.0" } @@ -57124,7 +57110,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", - "optional": true, "dependencies": { "utrie": "^1.0.2" } @@ -58653,7 +58638,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", - "optional": true, "dependencies": { "base64-arraybuffer": "^1.0.2" } @@ -62018,7 +62002,7 @@ "@types/rison": "0.0.6", "@types/seedrandom": "^2.4.28", "@vx/responsive": "^0.0.199", - "csstype": "^2.6.4", + "csstype": "^3.1.3", "d3-format": "^1.3.2", "d3-interpolate": "^1.4.0", "d3-scale": "^3.0.0", @@ -62112,6 +62096,11 @@ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==" }, + "packages/superset-ui-core/node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, "packages/superset-ui-core/node_modules/d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -67634,6 +67623,13 @@ "@emotion/unitless": "0.7.5", "@emotion/utils": "0.11.3", "csstype": "^2.5.7" + }, + "dependencies": { + "csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" + } } }, "@emotion/sheet": { @@ -77236,7 +77232,7 @@ "@types/rison": "0.0.6", "@types/seedrandom": "^2.4.28", "@vx/responsive": "^0.0.199", - "csstype": "^2.6.4", + "csstype": "^3.1.3", "d3-format": "^1.3.2", "d3-interpolate": "^1.4.0", "d3-scale": "^3.0.0", @@ -77291,6 +77287,11 @@ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==" }, + "csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, "d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -79581,8 +79582,7 @@ "@types/raf": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.2.tgz", - "integrity": "sha512-sM4HyDVlDFl4goOXPF+g9nNHJFZQGot+HgySjM4cRjqXzjdatcEvYrtG4Ia8XumR9T6k8G2tW9B7hnUj51Uf0A==", - "optional": true + "integrity": "sha512-sM4HyDVlDFl4goOXPF+g9nNHJFZQGot+HgySjM4cRjqXzjdatcEvYrtG4Ia8XumR9T6k8G2tW9B7hnUj51Uf0A==" }, "@types/range-parser": { "version": "1.2.4", @@ -79597,6 +79597,13 @@ "requires": { "@types/prop-types": "*", "csstype": "^2.2.0" + }, + "dependencies": { + "csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" + } } }, "@types/react-dom": { @@ -82994,8 +83001,7 @@ "base64-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", - "optional": true + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" }, "base64-js": { "version": "1.5.1", @@ -83856,7 +83862,6 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", - "optional": true, "requires": { "@babel/runtime": "^7.12.5", "@types/raf": "^3.4.0", @@ -84063,16 +84068,6 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, - "chromatic": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-6.7.4.tgz", - "integrity": "sha512-QW4i8RQsON0JVnFnRf+8y70aIJptvC0Oi/26YJ669Dl03WmJRpobNO5qWFPTiv3KFKMc1Qf6/qFsRVZCtn+bfA==", - "dev": true, - "requires": { - "@discoveryjs/json-ext": "^0.5.7", - "@types/webpack-env": "^1.17.0" - } - }, "chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", @@ -85469,7 +85464,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", - "optional": true, "requires": { "utrie": "^1.0.2" } @@ -86161,11 +86155,6 @@ } } }, - "csstype": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz", - "integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==" - }, "currencyformatter.js": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/currencyformatter.js/-/currencyformatter.js-1.0.5.tgz", @@ -91710,7 +91699,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", - "optional": true, "requires": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" @@ -104606,6 +104594,11 @@ "@emotion/weak-memoize": "0.2.5" } }, + "csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" + }, "dom-helpers": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz", @@ -106137,8 +106130,7 @@ "rgbcolor": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", - "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", - "optional": true + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==" }, "rimraf": { "version": "3.0.2", @@ -107295,8 +107287,7 @@ "stackblur-canvas": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.6.0.tgz", - "integrity": "sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==", - "optional": true + "integrity": "sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==" }, "stackframe": { "version": "1.2.1", @@ -107843,8 +107834,7 @@ "svg-pathdata": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", - "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", - "optional": true + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==" }, "svgo": { "version": "3.0.2", @@ -108455,7 +108445,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", - "optional": true, "requires": { "utrie": "^1.0.2" } @@ -109557,7 +109546,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", - "optional": true, "requires": { "base64-arraybuffer": "^1.0.2" } diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 153cdfce68a99..99511ae650361 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -1,6 +1,6 @@ { "name": "superset", - "version": "3.1.0", + "version": "3.1.2", "description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.", "keywords": [ "big", @@ -44,7 +44,6 @@ "build-instrumented": "cross-env NODE_ENV=production BABEL_ENV=instrumented webpack --mode=production --color", "build-storybook": "build-storybook", "check-translation": "prettier --check ../superset/translations/**/LC_MESSAGES/*.json", - "chromatic": "npx chromatic --skip 'dependabot/**' --only-changed", "clean-translation": "prettier --write ../superset/translations/**/LC_MESSAGES/*.json", "core:cover": "cross-env NODE_ENV=test jest --coverage --coverageThreshold='{\"global\":{\"statements\":100,\"branches\":100,\"functions\":100,\"lines\":100}}' --collectCoverageFrom='[\"packages/**/src/**/*.{js,ts}\", \"!packages/superset-ui-demo/**/*\"]' packages", "cover": "cross-env NODE_ENV=test jest --coverage", @@ -56,7 +55,6 @@ "plugins:build": "node ./scripts/build.js", "plugins:build-assets": "node ./scripts/copyAssets.js", "plugins:build-storybook": "cd packages/superset-ui-demo && npm run build-storybook", - "plugins:chromatic": "cd packages/superset-ui-demo && npm run chromatic", "plugins:create-conventional-version": "npm run prune && lerna version --conventional-commits --create-release github --no-private --yes", "plugins:create-minor-version": "npm run prune && lerna version minor --no-private --yes", "plugins:create-patch-version": "npm run prune && lerna version patch --no-private --yes", @@ -286,7 +284,6 @@ "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-jsx-remove-data-test-id": "^2.1.3", "babel-plugin-lodash": "^3.3.4", - "chromatic": "^6.7.4", "copy-webpack-plugin": "^9.1.0", "cross-env": "^5.2.1", "css-loader": "^6.8.1", diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/sharedControls.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/sharedControls.tsx index 341aaa0729f08..56f3bc246e421 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/sharedControls.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/sharedControls.tsx @@ -207,7 +207,7 @@ const time_grain_sqla: SharedControlConfig<'SelectControl'> = { choices: (datasource as Dataset)?.time_grain_sqla || [], }), visibility: ({ controls }) => { - if (!hasGenericChartAxes) { + if (!hasGenericChartAxes || !controls?.x_axis) { return true; } @@ -245,10 +245,10 @@ const row_limit: SharedControlConfig<'SelectControl'> = { freeForm: true, label: t('Row limit'), clearable: false, + mapStateToProps: state => ({ maxValue: state?.common?.conf?.SQL_MAX_ROW }), validators: [ legacyValidateInteger, - (v, state) => - validateMaxValue(v, state?.common?.conf?.SQL_MAX_ROW || DEFAULT_MAX_ROW), + (v, state) => validateMaxValue(v, state?.maxValue || DEFAULT_MAX_ROW), ], default: 10000, choices: formatSelectOptions(ROW_LIMIT_OPTIONS), diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts index 9314f8d33f3b9..83b711baaa42f 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts @@ -518,6 +518,7 @@ export type ControlFormItemSpec = { debounceDelay?: number; } & (T extends 'Select' ? { + allowNewOptions?: boolean; options: any; value?: string; defaultValue?: string; diff --git a/superset-frontend/packages/superset-ui-core/package.json b/superset-frontend/packages/superset-ui-core/package.json index 62deb7709b652..91d7d28a321f8 100644 --- a/superset-frontend/packages/superset-ui-core/package.json +++ b/superset-frontend/packages/superset-ui-core/package.json @@ -41,7 +41,7 @@ "@types/rison": "0.0.6", "@types/seedrandom": "^2.4.28", "@vx/responsive": "^0.0.199", - "csstype": "^2.6.4", + "csstype": "^3.1.3", "d3-format": "^1.3.2", "d3-interpolate": "^1.4.0", "d3-scale": "^3.0.0", diff --git a/superset-frontend/packages/superset-ui-core/src/number-format/NumberFormats.ts b/superset-frontend/packages/superset-ui-core/src/number-format/NumberFormats.ts index 605da5d30e7b7..3825430ca0b3d 100644 --- a/superset-frontend/packages/superset-ui-core/src/number-format/NumberFormats.ts +++ b/superset-frontend/packages/superset-ui-core/src/number-format/NumberFormats.ts @@ -35,20 +35,20 @@ const FLOAT_SIGNED = FLOAT_SIGNED_2_POINT; const INTEGER = ',d'; const INTEGER_SIGNED = '+,d'; +const PERCENT = ',.0%'; const PERCENT_1_POINT = ',.1%'; const PERCENT_2_POINT = ',.2%'; const PERCENT_3_POINT = ',.3%'; -const PERCENT = PERCENT_2_POINT; +const PERCENT_SIGNED = '+,.0%'; const PERCENT_SIGNED_1_POINT = '+,.1%'; const PERCENT_SIGNED_2_POINT = '+,.2%'; const PERCENT_SIGNED_3_POINT = '+,.3%'; -const PERCENT_SIGNED = PERCENT_SIGNED_2_POINT; +const SI = '.0s'; const SI_1_DIGIT = '.1s'; const SI_2_DIGIT = '.2s'; const SI_3_DIGIT = '.3s'; -const SI = SI_3_DIGIT; const SMART_NUMBER = 'SMART_NUMBER'; const SMART_NUMBER_SIGNED = 'SMART_NUMBER_SIGNED'; diff --git a/superset-frontend/packages/superset-ui-demo/storybook/shared/components/createQueryStory.tsx b/superset-frontend/packages/superset-ui-demo/storybook/shared/components/createQueryStory.tsx index eb1d39e41a441..d6396d1320736 100644 --- a/superset-frontend/packages/superset-ui-demo/storybook/shared/components/createQueryStory.tsx +++ b/superset-frontend/packages/superset-ui-demo/storybook/shared/components/createQueryStory.tsx @@ -96,8 +96,5 @@ export default function createQueryStory({ ); }; - story.parameters = { - chromatic: { disable: true }, - }; return story; } diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-connection/ConnectionStories.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-connection/ConnectionStories.tsx index 3f75837e91aa7..4a31d8de35664 100644 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-connection/ConnectionStories.tsx +++ b/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-connection/ConnectionStories.tsx @@ -41,7 +41,7 @@ export default { ], }; -export const configureCORS = () => { +export const ConfigureCORS = () => { const host = text('Superset App host for CORS request', 'localhost:8088'); const selectEndpoint = select('Endpoint', ENDPOINTS, ''); const customEndpoint = text('Custom Endpoint (override above)', ''); @@ -80,7 +80,3 @@ export const configureCORS = () => { ); }; - -configureCORS.parameters = { - chromatic: { disable: true }, -}; diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js index a6967301c6a6b..ef2c76ad68a34 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js +++ b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js @@ -177,15 +177,12 @@ function Heatmap(element, props) { } } - function ordScale(k, rangeBands, sortMethod) { + function ordScale(k, rangeBands, sortMethod, formatter) { let domain = {}; - const actualKeys = {}; // hack to preserve type of keys when number records.forEach(d => { domain[d[k]] = (domain[d[k]] || 0) + d.v; - actualKeys[d[k]] = d[k]; }); - // Not using object.keys() as it converts to strings - const keys = Object.keys(actualKeys).map(s => actualKeys[s]); + const keys = Object.keys(domain).map(k => formatter(k)); if (sortMethod === 'alpha_asc') { domain = keys.sort(cmp); } else if (sortMethod === 'alpha_desc') { @@ -250,12 +247,12 @@ function Heatmap(element, props) { hideYLabel(); } - const fp = getNumberFormatter(NumberFormats.PERCENT); + const fp = getNumberFormatter(NumberFormats.PERCENT_2_POINT); - const xScale = ordScale('x', null, sortXAxis); - const yScale = ordScale('y', null, sortYAxis); - const xRbScale = ordScale('x', [0, hmWidth], sortXAxis); - const yRbScale = ordScale('y', [hmHeight, 0], sortYAxis); + 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]; diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/transformProps.js b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/transformProps.js index fa531ef9da1d3..ddafe456a6a99 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/transformProps.js +++ b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/transformProps.js @@ -57,10 +57,14 @@ export default function transformProps(chartProps) { 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, diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/index.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/index.ts index 819964173ed85..fc4aa7fca0151 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/index.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/index.ts @@ -26,3 +26,5 @@ export { default as PathChartPlugin } from './layers/Path'; export { default as PolygonChartPlugin } from './layers/Polygon'; export { default as ScatterChartPlugin } from './layers/Scatter'; export { default as ScreengridChartPlugin } from './layers/Screengrid'; +export { default as ContourChartPlugin } from './layers/Contour'; +export { default as HeatmapChartPlugin } from './layers/Heatmap'; diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/Arc.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/Arc.tsx index 1bc19618b678d..7ad1870170410 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/Arc.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/Arc.tsx @@ -45,16 +45,16 @@ function setTooltipContent(formData: QueryFormData) {
{formData.dimension && ( )}
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Geojson/Geojson.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Geojson/Geojson.tsx index c8c9d4863ce9b..a5dc2a14a0386 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Geojson/Geojson.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Geojson/Geojson.tsx @@ -93,13 +93,13 @@ const recurseGeoJson = ( function setTooltipContent(o: JsonObject) { return ( - o.object.extraProps && ( + o.object?.extraProps && (
{Object.keys(o.object.extraProps).map((prop, index) => ( ))}
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/Heatmap.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/Heatmap.tsx index b491d6dba164c..5a1453459d9b9 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/Heatmap.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/Heatmap.tsx @@ -30,7 +30,7 @@ function setTooltipContent(o: JsonObject) {
); diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Path/Path.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Path/Path.tsx index c4f13f0e57f91..f2f5c35e3d46d 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Path/Path.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Path/Path.tsx @@ -29,7 +29,7 @@ import { Point } from '../../types'; function setTooltipContent(o: JsonObject) { return ( - o.object.extraProps && ( + o.object?.extraProps && (
{Object.keys(o.object.extraProps).map((prop, index) => ( { - const metricLabel = formData.metric.label || formData.metric; + const metricLabel = formData?.metric?.label || formData?.metric; return (
- {o.object.name && ( + {o.object?.name && ( )} - {o.object[formData.line_column] && ( + {o.object?.[formData?.line_column] && ( )} - {formData.metric && ( + {formData?.metric && ( )}
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/Scatter.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/Scatter.tsx index c529e5c1d9df3..3ce1dcf25ef3d 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/Scatter.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/Scatter.tsx @@ -48,17 +48,17 @@ function setTooltipContent( - {o.object.cat_color && ( + {o.object?.cat_color && ( )} - {o.object.metric && ( - + {o.object?.metric && ( + )}
); diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/Screengrid.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/Screengrid.tsx index 7e47cc9530c04..9584c8f53eed6 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/Screengrid.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/Screengrid.tsx @@ -45,12 +45,12 @@ function setTooltipContent(o: JsonObject) { ); diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/index.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/index.ts index b77d5bd12c49c..9747a50b1e761 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/index.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/index.ts @@ -25,6 +25,8 @@ import { getLayer as deck_scatter } from './Scatter/Scatter'; import { getLayer as deck_geojson } from './Geojson/Geojson'; import { getLayer as deck_arc } from './Arc/Arc'; import { getLayer as deck_polygon } from './Polygon/Polygon'; +import { getLayer as deck_heatmap } from './Heatmap/Heatmap'; +import { getLayer as deck_contour } from './Contour/Contour'; const layerGenerators = { deck_grid, @@ -35,6 +37,8 @@ const layerGenerators = { deck_geojson, deck_arc, deck_polygon, + deck_heatmap, + deck_contour, }; export default layerGenerators; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx index 112e21657f013..8a95a81d5f06b 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx @@ -354,6 +354,7 @@ export default styled(BigNumberVis)` .header-line { position: relative; line-height: 1em; + white-space: nowrap; span { position: absolute; bottom: 0; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts index f32993b1dfc25..231b0d65dac20 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts @@ -48,11 +48,12 @@ import { getDefaultTooltip } from '../utils/tooltip'; import { Refs } from '../types'; import { getColtypesMapping } from '../utils/series'; -const setIntervalBoundsAndColors = ( +export const getIntervalBoundsAndColors = ( intervals: string, intervalColorIndices: string, colorFn: CategoricalColorScale, - normalizer: number, + min: number, + max: number, ): Array<[number, string]> => { let intervalBoundsNonNormalized; let intervalColorIndicesArray; @@ -65,7 +66,7 @@ const setIntervalBoundsAndColors = ( } const intervalBounds = intervalBoundsNonNormalized.map( - bound => bound / normalizer, + bound => (bound - min) / (max - min), ); const intervalColors = intervalColorIndicesArray.map( ind => colorFn.colors[(ind - 1) % colorFn.colors.length], @@ -221,12 +222,12 @@ export default function transformProps( const axisLabelLength = Math.max( ...axisLabels.map(label => numberFormatter(label).length).concat([1]), ); - const normalizer = max; - const intervalBoundsAndColors = setIntervalBoundsAndColors( + const intervalBoundsAndColors = getIntervalBoundsAndColors( intervals, intervalColorIndices, colorFn, - normalizer, + min, + max, ); const splitLineDistance = axisLineWidth + splitLineLength + OFFSETS.ticksFromLine; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts index 1d4eceb33f3b6..8eecb6de4723c 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts @@ -406,8 +406,9 @@ export default function transformProps( rawSeriesB.forEach(entry => { const entryName = String(entry.name || ''); - const seriesName = `${inverted[entryName] || entryName} (1)`; - const colorScaleKey = getOriginalSeries(seriesName, array); + const seriesEntry = inverted[entryName] || entryName; + const seriesName = `${seriesEntry} (1)`; + const colorScaleKey = getOriginalSeries(seriesEntry, array); const seriesFormatter = getFormatter( customFormattersSecondary, @@ -531,6 +532,7 @@ export default function transformProps( !!contributionMode, customFormatters, formatter, + yAxisFormat, ), }, scale: truncateYAxis, @@ -553,6 +555,7 @@ export default function transformProps( !!contributionMode, customFormattersSecondary, formatterSecondary, + yAxisFormatSecondary, ), }, scale: truncateYAxis, 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 3bbe285aeca54..78245cc9d9d52 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts @@ -95,6 +95,7 @@ import { } from '../constants'; import { getDefaultTooltip } from '../utils/tooltip'; import { + getPercentFormatter, getTooltipTimeFormatter, getXAxisFormatter, getYAxisFormatter, @@ -253,7 +254,7 @@ export default function transformProps( const series: SeriesOption[] = []; const forcePercentFormatter = Boolean(contributionMode || isAreaExpand); - const percentFormatter = getNumberFormatter(',.0%'); + const percentFormatter = getPercentFormatter(yAxisFormat); const defaultFormatter = currencyFormat?.symbol ? new CurrencyFormatter({ d3Format: yAxisFormat, currency: currencyFormat }) : getNumberFormatter(yAxisFormat); @@ -437,6 +438,7 @@ export default function transformProps( yAxisTitlePosition, convertInteger(yAxisTitleMargin), convertInteger(xAxisTitleMargin), + isHorizontal, ); const legendData = rawSeries @@ -485,6 +487,7 @@ export default function transformProps( forcePercentFormatter, customFormatters, defaultFormatter, + yAxisFormat, ), }, scale: truncateYAxis, @@ -572,7 +575,6 @@ export default function transformProps( right: TIMESERIES_CONSTANTS.toolboxRight, feature: { dataZoom: { - yAxisIndex: false, title: { zoom: t('zoom area'), back: t('restore zoom'), @@ -587,6 +589,7 @@ export default function transformProps( start: TIMESERIES_CONSTANTS.dataZoomStart, end: TIMESERIES_CONSTANTS.dataZoomEnd, bottom: TIMESERIES_CONSTANTS.zoomBottom, + yAxisIndex: isHorizontal ? 0 : undefined, }, ] : [], 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 3b62417f16335..be89fdfc74c96 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts @@ -550,6 +550,7 @@ export function getPadding( yAxisTitlePosition?: string, yAxisTitleMargin?: number, xAxisTitleMargin?: number, + isHorizontal?: boolean, ): { bottom: number; left: number; @@ -560,21 +561,29 @@ export function getPadding( ? TIMESERIES_CONSTANTS.yAxisLabelTopOffset : 0; const xAxisOffset = addXAxisTitleOffset ? Number(xAxisTitleMargin) || 0 : 0; - return getChartPadding(showLegend, legendOrientation, margin, { - top: - yAxisTitlePosition && yAxisTitlePosition === 'Top' - ? TIMESERIES_CONSTANTS.gridOffsetTop + (Number(yAxisTitleMargin) || 0) - : TIMESERIES_CONSTANTS.gridOffsetTop + yAxisOffset, - bottom: zoomable - ? TIMESERIES_CONSTANTS.gridOffsetBottomZoomable + xAxisOffset - : TIMESERIES_CONSTANTS.gridOffsetBottom + xAxisOffset, - left: - yAxisTitlePosition === 'Left' - ? TIMESERIES_CONSTANTS.gridOffsetLeft + (Number(yAxisTitleMargin) || 0) - : TIMESERIES_CONSTANTS.gridOffsetLeft, - right: - showLegend && legendOrientation === LegendOrientation.Right - ? 0 - : TIMESERIES_CONSTANTS.gridOffsetRight, - }); + return getChartPadding( + showLegend, + legendOrientation, + margin, + { + top: + yAxisTitlePosition && yAxisTitlePosition === 'Top' + ? TIMESERIES_CONSTANTS.gridOffsetTop + (Number(yAxisTitleMargin) || 0) + : TIMESERIES_CONSTANTS.gridOffsetTop + yAxisOffset, + bottom: + zoomable && !isHorizontal + ? TIMESERIES_CONSTANTS.gridOffsetBottomZoomable + xAxisOffset + : TIMESERIES_CONSTANTS.gridOffsetBottom + xAxisOffset, + left: + yAxisTitlePosition === 'Left' + ? TIMESERIES_CONSTANTS.gridOffsetLeft + + (Number(yAxisTitleMargin) || 0) + : TIMESERIES_CONSTANTS.gridOffsetLeft, + right: + showLegend && legendOrientation === LegendOrientation.Right + ? 0 + : TIMESERIES_CONSTANTS.gridOffsetRight, + }, + isHorizontal, + ); } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts index 5416fa1577635..a8f9d2aa31d5a 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts @@ -23,6 +23,7 @@ import { getNumberFormatter, getTimeFormatter, isSavedMetric, + NumberFormats, QueryFormMetric, smartDateDetailedFormatter, smartDateFormatter, @@ -30,14 +31,22 @@ import { ValueFormatter, } from '@superset-ui/core'; +export const getPercentFormatter = (format?: string) => + getNumberFormatter( + !format || format === NumberFormats.SMART_NUMBER + ? NumberFormats.PERCENT + : format, + ); + export const getYAxisFormatter = ( metrics: QueryFormMetric[], forcePercentFormatter: boolean, customFormatters: Record, defaultFormatter: ValueFormatter, + format?: string, ) => { if (forcePercentFormatter) { - return getNumberFormatter(',.0%'); + return getPercentFormatter(format); } const metricsArray = ensureIsArray(metrics); if ( diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts index 6a51e7cbf7c5c..8c87bf4333c61 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts @@ -466,6 +466,7 @@ export function getChartPadding( orientation: LegendOrientation, margin?: string | number | null, padding?: { top?: number; bottom?: number; left?: number; right?: number }, + isHorizontal?: boolean, ): { bottom: number; left: number; @@ -486,6 +487,19 @@ export function getChartPadding( } const { bottom = 0, left = 0, right = 0, top = 0 } = padding || {}; + + if (isHorizontal) { + return { + left: + left + (orientation === LegendOrientation.Bottom ? legendMargin : 0), + right: + right + (orientation === LegendOrientation.Right ? legendMargin : 0), + top: top + (orientation === LegendOrientation.Top ? legendMargin : 0), + bottom: + bottom + (orientation === LegendOrientation.Left ? legendMargin : 0), + }; + } + return { left: left + (orientation === LegendOrientation.Left ? legendMargin : 0), right: right + (orientation === LegendOrientation.Right ? legendMargin : 0), diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Gauge/transformProps.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Gauge/transformProps.test.ts index 915a8b9e9dd37..760e3ff93c2b4 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/Gauge/transformProps.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/Gauge/transformProps.test.ts @@ -16,8 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import { ChartProps, SqlaFormData, supersetTheme } from '@superset-ui/core'; -import transformProps from '../../src/Gauge/transformProps'; +import { + CategoricalColorNamespace, + ChartProps, + SqlaFormData, + supersetTheme, +} from '@superset-ui/core'; +import transformProps, { + getIntervalBoundsAndColors, +} from '../../src/Gauge/transformProps'; import { EchartsGaugeChartProps } from '../../src/Gauge/types'; describe('Echarts Gauge transformProps', () => { @@ -256,8 +263,9 @@ describe('Echarts Gauge transformProps', () => { const formData: SqlaFormData = { ...baseFormData, groupby: ['year', 'platform'], - intervals: '50,100', + intervals: '60,100', intervalColorIndices: '1,2', + minVal: 20, }; const queriesData = [ { @@ -342,3 +350,43 @@ describe('Echarts Gauge transformProps', () => { ); }); }); + +describe('getIntervalBoundsAndColors', () => { + it('should generate correct interval bounds and colors', () => { + const colorFn = CategoricalColorNamespace.getScale( + 'supersetColors' as string, + ); + expect(getIntervalBoundsAndColors('', '', colorFn, 0, 10)).toEqual([]); + expect(getIntervalBoundsAndColors('4, 10', '1, 2', colorFn, 0, 10)).toEqual( + [ + [0.4, '#1f77b4'], + [1, '#ff7f0e'], + ], + ); + expect( + getIntervalBoundsAndColors('4, 8, 10', '9, 8, 7', colorFn, 0, 10), + ).toEqual([ + [0.4, '#bcbd22'], + [0.8, '#7f7f7f'], + [1, '#e377c2'], + ]); + expect(getIntervalBoundsAndColors('4, 10', '1, 2', colorFn, 2, 10)).toEqual( + [ + [0.25, '#1f77b4'], + [1, '#ff7f0e'], + ], + ); + expect( + getIntervalBoundsAndColors('-4, 0', '1, 2', colorFn, -10, 0), + ).toEqual([ + [0.6, '#1f77b4'], + [1, '#ff7f0e'], + ]); + expect( + getIntervalBoundsAndColors('-4, -2', '1, 2', colorFn, -10, -2), + ).toEqual([ + [0.75, '#1f77b4'], + [1, '#ff7f0e'], + ]); + }); +}); diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/utils/formatters.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/utils/formatters.test.ts new file mode 100644 index 0000000000000..f8d40a5bd136c --- /dev/null +++ b/superset-frontend/plugins/plugin-chart-echarts/test/utils/formatters.test.ts @@ -0,0 +1,37 @@ +/** + * 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 { NumberFormats } from '@superset-ui/core'; +import { getPercentFormatter } from '../../src/utils/formatters'; + +describe('getPercentFormatter', () => { + const value = 0.6; + it('should format as percent if no format is specified', () => { + expect(getPercentFormatter().format(value)).toEqual('60%'); + }); + it('should format as percent if SMART_NUMBER is specified', () => { + expect( + getPercentFormatter(NumberFormats.SMART_NUMBER).format(value), + ).toEqual('60%'); + }); + it('should format using a provided format', () => { + expect( + getPercentFormatter(NumberFormats.PERCENT_2_POINT).format(value), + ).toEqual('60.00%'); + }); +}); diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts index 3d7d21c8d0b02..10768a47f8d4d 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts @@ -795,6 +795,14 @@ describe('getChartPadding', () => { right: 0, top: 0, }); + expect( + getChartPadding(true, LegendOrientation.Left, 100, undefined, true), + ).toEqual({ + bottom: 100, + left: 0, + right: 0, + top: 0, + }); }); it('should return the correct padding for right orientation', () => { @@ -804,6 +812,14 @@ describe('getChartPadding', () => { right: 50, top: 0, }); + expect( + getChartPadding(true, LegendOrientation.Right, 50, undefined, true), + ).toEqual({ + bottom: 0, + left: 0, + right: 50, + top: 0, + }); }); it('should return the correct padding for top orientation', () => { @@ -813,6 +829,14 @@ describe('getChartPadding', () => { right: 0, top: 20, }); + expect( + getChartPadding(true, LegendOrientation.Top, 20, undefined, true), + ).toEqual({ + bottom: 0, + left: 0, + right: 0, + top: 20, + }); }); it('should return the correct padding for bottom orientation', () => { @@ -822,6 +846,14 @@ describe('getChartPadding', () => { right: 0, top: 0, }); + expect( + getChartPadding(true, LegendOrientation.Bottom, 10, undefined, true), + ).toEqual({ + bottom: 0, + left: 10, + right: 0, + top: 0, + }); }); }); diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.jsx b/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.jsx index 760ff90c15d8f..41063c290f3fa 100644 --- a/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.jsx +++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.jsx @@ -24,7 +24,11 @@ import { PivotData, flatKey } from './utilities'; import { Styles } from './Styles'; const parseLabel = value => { - if (typeof value === 'number' || typeof value === 'string') { + if (typeof value === 'string') { + if (value === 'metric') return t('metric'); + return value; + } + if (typeof value === 'number') { return value; } return String(value); diff --git a/superset-frontend/plugins/plugin-chart-table/src/DataTable/hooks/useSticky.tsx b/superset-frontend/plugins/plugin-chart-table/src/DataTable/hooks/useSticky.tsx index 067d071ee1926..e154a521f6922 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/DataTable/hooks/useSticky.tsx +++ b/superset-frontend/plugins/plugin-chart-table/src/DataTable/hooks/useSticky.tsx @@ -226,6 +226,7 @@ function StickyWrap({ height: maxHeight, overflow: 'auto', visibility: 'hidden', + scrollbarGutter: 'stable', }} > {React.cloneElement(table, {}, theadWithRef, tbody, tfootWithRef)} @@ -252,6 +253,7 @@ function StickyWrap({ ref={scrollHeaderRef} style={{ overflow: 'hidden', + scrollbarGutter: 'stable', }} > {React.cloneElement( @@ -270,6 +272,7 @@ function StickyWrap({ ref={scrollFooterRef} style={{ overflow: 'hidden', + scrollbarGutter: 'stable', }} > {React.cloneElement( @@ -297,6 +300,7 @@ function StickyWrap({ style={{ height: bodyHeight, overflow: 'auto', + scrollbarGutter: 'stable', }} onScroll={sticky.hasHorizontalScroll ? onScroll : undefined} > diff --git a/superset-frontend/plugins/plugin-chart-table/src/utils/formatValue.ts b/superset-frontend/plugins/plugin-chart-table/src/utils/formatValue.ts index 139f92336c12b..043fa6b59d500 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/utils/formatValue.ts +++ b/superset-frontend/plugins/plugin-chart-table/src/utils/formatValue.ts @@ -17,6 +17,7 @@ * under the License. */ import { + CurrencyFormatter, DataRecordValue, GenericDataType, getNumberFormatter, @@ -64,6 +65,11 @@ export function formatColumnValue( const smallNumberFormatter = config.d3SmallNumberFormat === undefined ? formatter + : config.currencyFormat + ? new CurrencyFormatter({ + d3Format: config.d3SmallNumberFormat, + currency: config.currencyFormat, + }) : getNumberFormatter(config.d3SmallNumberFormat); return formatValue( isNumber && typeof value === 'number' && Math.abs(value) < 1 diff --git a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx index d6998476baa2b..52cfab166215f 100644 --- a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx +++ b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx @@ -166,6 +166,48 @@ describe('plugin-chart-table', () => { expect(cells[2]).toHaveTextContent('$ 0'); }); + it('render small formatted data with currencies', () => { + const props = transformProps({ + ...testData.raw, + rawFormData: { + ...testData.raw.rawFormData, + column_config: { + num: { + d3SmallNumberFormat: '.2r', + currencyFormat: { symbol: 'USD', symbolPosition: 'prefix' }, + }, + }, + }, + queriesData: [ + { + ...testData.raw.queriesData[0], + data: [ + { + num: 1234, + }, + { + num: 0.5, + }, + { + num: 0.61234, + }, + ], + }, + ], + }); + render( + ProviderWrapper({ + children: , + }), + ); + const cells = document.querySelectorAll('td'); + + expect(document.querySelectorAll('th')[0]).toHaveTextContent('num'); + expect(cells[0]).toHaveTextContent('$ 1.23k'); + expect(cells[1]).toHaveTextContent('$ 0.50'); + expect(cells[2]).toHaveTextContent('$ 0.61'); + }); + it('render empty data', () => { wrap.setProps({ ...transformProps(testData.empty), sticky: false }); tree = wrap.render(); diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.js b/superset-frontend/src/SqlLab/actions/sqlLab.js index 567d3383d752d..72dc03f001845 100644 --- a/superset-frontend/src/SqlLab/actions/sqlLab.js +++ b/superset-frontend/src/SqlLab/actions/sqlLab.js @@ -476,6 +476,7 @@ export function migrateQueryEditorFromLocalStorage( const newQueryEditor = { ...queryEditor, id: json.id.toString(), + inLocalStorage: false, }; dispatch({ type: MIGRATE_QUERY_EDITOR, @@ -741,15 +742,18 @@ export function removeQueryEditor(queryEditor) { return sync .then(() => dispatch({ type: REMOVE_QUERY_EDITOR, queryEditor })) - .catch(() => - dispatch( - addDangerToast( - t( - 'An error occurred while removing tab. Please contact your administrator.', + .catch(({ status }) => { + if (status !== 404) { + return dispatch( + addDangerToast( + t( + 'An error occurred while removing tab. Please contact your administrator.', + ), ), - ), - ), - ); + ); + } + return dispatch({ type: REMOVE_QUERY_EDITOR, queryEditor }); + }); }; } @@ -1121,9 +1125,11 @@ export function removeTables(tables) { const sync = isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE) ? Promise.all( tablesToRemove.map(table => - SupersetClient.delete({ - endpoint: encodeURI(`/tableschemaview/${table.id}`), - }), + table.initialized + ? SupersetClient.delete({ + endpoint: encodeURI(`/tableschemaview/${table.id}`), + }) + : Promise.resolve(), ), ) : Promise.resolve(); diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.test.js b/superset-frontend/src/SqlLab/actions/sqlLab.test.js index 175ea06ec3ecf..20fd53ad389c9 100644 --- a/superset-frontend/src/SqlLab/actions/sqlLab.test.js +++ b/superset-frontend/src/SqlLab/actions/sqlLab.test.js @@ -883,7 +883,7 @@ describe('async actions', () => { it('updates the table schema state in the backend', () => { expect.assertions(2); - const table = { id: 1 }; + const table = { id: 1, initialized: true }; const store = mockStore({}); const expectedActions = [ { @@ -900,7 +900,10 @@ describe('async actions', () => { it('deletes multiple tables and updates the table schema state in the backend', () => { expect.assertions(2); - const tables = [{ id: 1 }, { id: 2 }]; + const tables = [ + { id: 1, initialized: true }, + { id: 2, initialized: true }, + ]; const store = mockStore({}); const expectedActions = [ { @@ -913,6 +916,23 @@ describe('async actions', () => { expect(fetchMock.calls(updateTableSchemaEndpoint)).toHaveLength(2); }); }); + + it('only updates the initialized table schema state in the backend', () => { + expect.assertions(2); + + const tables = [{ id: 1 }, { id: 2, initialized: true }]; + const store = mockStore({}); + const expectedActions = [ + { + type: actions.REMOVE_TABLES, + tables, + }, + ]; + return store.dispatch(actions.removeTables(tables)).then(() => { + expect(store.getActions()).toEqual(expectedActions); + expect(fetchMock.calls(updateTableSchemaEndpoint)).toHaveLength(1); + }); + }); }); describe('migrateQueryEditorFromLocalStorage', () => { @@ -937,12 +957,17 @@ describe('async actions', () => { { ...query, id: 'previewTwo' }, ]; const store = mockStore({}); + const oldQueryEditor = { ...queryEditor, inLocalStorage: true }; const expectedActions = [ { type: actions.MIGRATE_QUERY_EDITOR, - oldQueryEditor: queryEditor, + oldQueryEditor, // new qe has a different id - newQueryEditor: { ...queryEditor, id: '1' }, + newQueryEditor: { + ...oldQueryEditor, + id: '1', + inLocalStorage: false, + }, }, { type: actions.MIGRATE_TAB_HISTORY, @@ -975,7 +1000,7 @@ describe('async actions', () => { return store .dispatch( actions.migrateQueryEditorFromLocalStorage( - queryEditor, + oldQueryEditor, tables, queries, ), diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.tsx b/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.tsx index 63f67170d05ff..152a306ecfde0 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.tsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.tsx @@ -17,6 +17,7 @@ * under the License. */ import React from 'react'; +import * as uiCore from '@superset-ui/core'; import { act } from 'react-dom/test-utils'; import { fireEvent, render, waitFor } from 'spec/helpers/testing-library'; import fetchMock from 'fetch-mock'; @@ -31,7 +32,7 @@ import { import SqlEditorLeftBar from 'src/SqlLab/components/SqlEditorLeftBar'; import ResultSet from 'src/SqlLab/components/ResultSet'; import { api } from 'src/hooks/apiResources/queryApi'; -import { getExtensionsRegistry } from '@superset-ui/core'; +import { getExtensionsRegistry, FeatureFlag } from '@superset-ui/core'; import setupExtensions from 'src/setup/setupExtensions'; import type { Action, Middleware, Store } from 'redux'; import SqlEditor, { Props } from '.'; @@ -63,6 +64,7 @@ fetchMock.get('glob:*/api/v1/database/*/function_names/', { }); fetchMock.get('glob:*/api/v1/database/*', { result: [] }); fetchMock.get('glob:*/api/v1/database/*/tables/*', { options: [] }); +fetchMock.get('glob:*/tabstateview/*', defaultQueryEditor); fetchMock.post('glob:*/sqllab/execute/*', { result: [] }); let store: Store; @@ -290,4 +292,43 @@ describe('SqlEditor', () => { await findByText('sqleditor.extension.form extension component'), ).toBeInTheDocument(); }); + + describe('with SqllabBackendPersistence enabled', () => { + let isFeatureEnabledMock: jest.MockInstance< + boolean, + [feature: FeatureFlag] + >; + beforeEach(() => { + isFeatureEnabledMock = jest + .spyOn(uiCore, 'isFeatureEnabled') + .mockImplementation( + featureFlag => + featureFlag === uiCore.FeatureFlag.SQLLAB_BACKEND_PERSISTENCE, + ); + }); + afterEach(() => { + isFeatureEnabledMock.mockClear(); + }); + + it('should render loading state when its Editor is not loaded', async () => { + const switchTabApi = `glob:*/tabstateview/${defaultQueryEditor.id}/activate`; + fetchMock.post(switchTabApi, {}); + const { getByTestId } = setup( + { + ...mockedProps, + queryEditor: { + ...mockedProps.queryEditor, + loaded: false, + }, + }, + store, + ); + const indicator = getByTestId('sqlEditor-loading'); + expect(indicator).toBeInTheDocument(); + await waitFor(() => + expect(fetchMock.calls('glob:*/tabstateview/*').length).toBe(1), + ); + expect(fetchMock.calls(switchTabApi).length).toBe(1); + }); + }); }); diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx index 088143448731a..3970eae513eec 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx @@ -51,7 +51,7 @@ import Mousetrap from 'mousetrap'; import Button from 'src/components/Button'; import Timer from 'src/components/Timer'; import ResizableSidebar from 'src/components/ResizableSidebar'; -import { AntdDropdown, AntdSwitch } from 'src/components'; +import { AntdDropdown, AntdSwitch, Skeleton } from 'src/components'; import { Input } from 'src/components/Input'; import { Menu } from 'src/components/Menu'; import Icons from 'src/components/Icons'; @@ -73,6 +73,7 @@ import { setActiveSouthPaneTab, updateSavedQuery, formatQuery, + switchQueryEditor, } from 'src/SqlLab/actions/sqlLab'; import { STATE_TYPE_MAP, @@ -489,6 +490,16 @@ const SqlEditor: React.FC = ({ } }); + const shouldLoadQueryEditor = + isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE) && + !queryEditor.loaded; + + const loadQueryEditor = useEffectEvent(() => { + if (shouldLoadQueryEditor) { + dispatch(switchQueryEditor(queryEditor, displayLimit)); + } + }); + useEffect(() => { // We need to measure the height of the sql editor post render to figure the height of // the south pane so it gets rendered properly @@ -498,6 +509,7 @@ const SqlEditor: React.FC = ({ WINDOW_RESIZE_THROTTLE_MS, ); + loadQueryEditor(); window.addEventListener('resize', handleWindowResizeWithThrottle); window.addEventListener('beforeunload', onBeforeUnload); @@ -506,7 +518,7 @@ const SqlEditor: React.FC = ({ window.removeEventListener('beforeunload', onBeforeUnload); }; // TODO: Remove useEffectEvent deps once https://github.com/facebook/react/pull/25881 is released - }, [onBeforeUnload]); + }, [onBeforeUnload, loadQueryEditor]); useEffect(() => { if (!database || isEmpty(database)) { @@ -841,7 +853,17 @@ const SqlEditor: React.FC = ({ )} - {showEmptyState ? ( + {shouldLoadQueryEditor ? ( +
+ +
+ ) : showEmptyState ? ( 0 + ? newState.queryEditors.slice(-1).map(qe => qe.id) + : tabHistory, tables, queries, unsavedQueryEditor: { diff --git a/superset-frontend/src/SqlLab/reducers/sqlLab.test.js b/superset-frontend/src/SqlLab/reducers/sqlLab.test.js index e1a234734bca6..041835d04b8e6 100644 --- a/superset-frontend/src/SqlLab/reducers/sqlLab.test.js +++ b/superset-frontend/src/SqlLab/reducers/sqlLab.test.js @@ -75,6 +75,25 @@ describe('sqlLabReducer', () => { initialState.queryEditors.length, ); }); + it('should select the latest query editor when tabHistory is empty', () => { + const currentQE = newState.queryEditors[0]; + newState = { + ...initialState, + tabHistory: [initialState.queryEditors[0]], + }; + const action = { + type: actions.REMOVE_QUERY_EDITOR, + queryEditor: currentQE, + }; + newState = sqlLabReducer(newState, action); + expect(newState.queryEditors).toHaveLength( + initialState.queryEditors.length - 1, + ); + expect(newState.queryEditors.map(qe => qe.id)).not.toContainEqual( + currentQE.id, + ); + expect(newState.tabHistory).toEqual([initialState.queryEditors[2].id]); + }); it('should remove a query editor including unsaved changes', () => { expect(newState.queryEditors).toHaveLength( initialState.queryEditors.length + 1, diff --git a/superset-frontend/src/components/AsyncAceEditor/index.tsx b/superset-frontend/src/components/AsyncAceEditor/index.tsx index 2e499e150bb40..1b755f50ef3d1 100644 --- a/superset-frontend/src/components/AsyncAceEditor/index.tsx +++ b/superset-frontend/src/components/AsyncAceEditor/index.tsx @@ -24,11 +24,15 @@ import { TextMode as OrigTextMode, } from 'brace'; import AceEditor, { IAceEditorProps } from 'react-ace'; +import { config } from 'ace-builds'; import { acequire } from 'ace-builds/src-noconflict/ace'; import AsyncEsmComponent, { PlaceholderProps, } from 'src/components/AsyncEsmComponent'; import useEffectEvent from 'src/hooks/useEffectEvent'; +import cssWorkerUrl from 'ace-builds/src-noconflict/worker-css'; + +config.setModuleUrl('ace/mode/css_worker', cssWorkerUrl); export interface AceCompleterKeywordData { name: string; diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx index d1e8b39e0c963..5053a7671135c 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx @@ -54,11 +54,12 @@ import { LOG_ACTIONS_DRILL_BY_MODAL_OPENED, LOG_ACTIONS_FURTHER_DRILL_BY, } from 'src/logger/LogUtils'; +import { getQuerySettings } from 'src/explore/exploreUtils'; import { Dataset, DrillByType } from '../types'; import DrillByChart from './DrillByChart'; import { ContextMenuItem } from '../ChartContextMenu/ChartContextMenu'; import { useContextMenu } from '../ChartContextMenu/useContextMenu'; -import { getChartDataRequest } from '../chartAction'; +import { getChartDataRequest, handleChartDataResponse } from '../chartAction'; import { useDisplayModeToggle } from './useDisplayModeToggle'; import { DrillByBreadcrumb, @@ -390,13 +391,17 @@ export default function DrillByModal({ useEffect(() => { if (drilledFormData) { + const [useLegacyApi] = getQuerySettings(drilledFormData); setIsChartDataLoading(true); setChartDataResult(undefined); getChartDataRequest({ formData: drilledFormData, }) - .then(({ json }) => { - setChartDataResult(json.result); + .then(({ response, json }) => + handleChartDataResponse(response, json, useLegacyApi), + ) + .then(queriesResponse => { + setChartDataResult(queriesResponse); }) .catch(() => { addDangerToast(t('Failed to load chart data.')); diff --git a/superset-frontend/src/components/Chart/chartAction.js b/superset-frontend/src/components/Chart/chartAction.js index 8cd3785ae5156..86e4b6b2a06c6 100644 --- a/superset-frontend/src/components/Chart/chartAction.js +++ b/superset-frontend/src/components/Chart/chartAction.js @@ -374,6 +374,29 @@ export function addChart(chart, key) { return { type: ADD_CHART, chart, key }; } +export function handleChartDataResponse(response, json, useLegacyApi) { + if (isFeatureEnabled(FeatureFlag.GLOBAL_ASYNC_QUERIES)) { + // deal with getChartDataRequest transforming the response data + const result = 'result' in json ? json.result : json; + switch (response.status) { + case 200: + // Query results returned synchronously, meaning query was already cached. + return Promise.resolve(result); + case 202: + // Query is running asynchronously and we must await the results + if (useLegacyApi) { + return waitForAsyncData(result[0]); + } + return waitForAsyncData(result); + default: + throw new Error( + `Received unexpected response status (${response.status}) while fetching chart data`, + ); + } + } + return json.result; +} + export function exploreJSON( formData, force = false, @@ -409,31 +432,11 @@ export function exploreJSON( dispatch(chartUpdateStarted(controller, formData, key)); + const [useLegacyApi] = getQuerySettings(formData); const chartDataRequestCaught = chartDataRequest - .then(({ response, json }) => { - if (isFeatureEnabled(FeatureFlag.GLOBAL_ASYNC_QUERIES)) { - // deal with getChartDataRequest transforming the response data - const result = 'result' in json ? json.result : json; - const [useLegacyApi] = getQuerySettings(formData); - switch (response.status) { - case 200: - // Query results returned synchronously, meaning query was already cached. - return Promise.resolve(result); - case 202: - // Query is running asynchronously and we must await the results - if (useLegacyApi) { - return waitForAsyncData(result[0]); - } - return waitForAsyncData(result); - default: - throw new Error( - `Received unexpected response status (${response.status}) while fetching chart data`, - ); - } - } - - return json.result; - }) + .then(({ response, json }) => + handleChartDataResponse(response, json, useLegacyApi), + ) .then(queriesResponse => { queriesResponse.forEach(resultItem => dispatch( @@ -492,7 +495,6 @@ export function exploreJSON( }); // only retrieve annotations when calling the legacy API - const [useLegacyApi] = getQuerySettings(formData); const annotationLayers = useLegacyApi ? formData.annotation_layers || [] : []; diff --git a/superset-frontend/src/components/Chart/chartActions.test.js b/superset-frontend/src/components/Chart/chartActions.test.js index b3a6fed9f5c15..7babb7bbb2465 100644 --- a/superset-frontend/src/components/Chart/chartActions.test.js +++ b/superset-frontend/src/components/Chart/chartActions.test.js @@ -21,10 +21,12 @@ import fetchMock from 'fetch-mock'; import sinon from 'sinon'; import * as chartlib from '@superset-ui/core'; -import { SupersetClient } from '@superset-ui/core'; +import { FeatureFlag, SupersetClient } from '@superset-ui/core'; import { LOG_EVENT } from 'src/logger/actions'; import * as exploreUtils from 'src/explore/exploreUtils'; import * as actions from 'src/components/Chart/chartAction'; +import * as asyncEvent from 'src/middleware/asyncEvent'; +import { handleChartDataResponse } from 'src/components/Chart/chartAction'; describe('chart actions', () => { const MOCK_URL = '/mockURL'; @@ -33,6 +35,7 @@ describe('chart actions', () => { let getChartDataUriStub; let metadataRegistryStub; let buildQueryRegistryStub; + let waitForAsyncDataStub; let fakeMetadata; const setupDefaultFetchMock = () => { @@ -66,6 +69,9 @@ describe('chart actions', () => { result_format: 'json', }), })); + waitForAsyncDataStub = sinon + .stub(asyncEvent, 'waitForAsyncData') + .callsFake(data => Promise.resolve(data)); }); afterEach(() => { @@ -74,6 +80,11 @@ describe('chart actions', () => { fetchMock.resetHistory(); metadataRegistryStub.restore(); buildQueryRegistryStub.restore(); + waitForAsyncDataStub.restore(); + + global.featureFlags = { + [FeatureFlag.GLOBAL_ASYNC_QUERIES]: false, + }; }); describe('v1 API', () => { @@ -114,6 +125,36 @@ describe('chart actions', () => { expect(fetchMock.calls(mockBigIntUrl)).toHaveLength(1); expect(json.value.toString()).toEqual(expectedBigNumber); }); + + it('handleChartDataResponse should return result if GlobalAsyncQueries flag is disabled', async () => { + const result = await handleChartDataResponse( + { status: 200 }, + { result: [1, 2, 3] }, + ); + expect(result).toEqual([1, 2, 3]); + }); + + it('handleChartDataResponse should handle responses when GlobalAsyncQueries flag is enabled and results are returned synchronously', async () => { + global.featureFlags = { + [FeatureFlag.GLOBAL_ASYNC_QUERIES]: true, + }; + const result = await handleChartDataResponse( + { status: 200 }, + { result: [1, 2, 3] }, + ); + expect(result).toEqual([1, 2, 3]); + }); + + it('handleChartDataResponse should handle responses when GlobalAsyncQueries flag is enabled and query is running asynchronously', async () => { + global.featureFlags = { + [FeatureFlag.GLOBAL_ASYNC_QUERIES]: true, + }; + const result = await handleChartDataResponse( + { status: 202 }, + { result: [1, 2, 3] }, + ); + expect(result).toEqual([1, 2, 3]); + }); }); describe('legacy API', () => { diff --git a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx index 874d22ea6bb2b..18e676991261b 100644 --- a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx +++ b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx @@ -229,6 +229,32 @@ test('Should database select display options', async () => { expect(await screen.findByText('test-mysql')).toBeInTheDocument(); }); +test('Should fetch the search keyword when total count exceeds initial options', async () => { + fetchMock.get( + databaseApiRoute, + { + ...fakeDatabaseApiResult, + count: fakeDatabaseApiResult.result.length + 1, + }, + { overwriteRoutes: true }, + ); + + const props = createProps(); + render(, { useRedux: true, store }); + const select = screen.getByRole('combobox', { + name: 'Select database or type to search databases', + }); + await waitFor(() => + expect(fetchMock.calls(databaseApiRoute)).toHaveLength(1), + ); + expect(select).toBeInTheDocument(); + userEvent.type(select, 'keywordtest'); + await waitFor(() => + expect(fetchMock.calls(databaseApiRoute)).toHaveLength(2), + ); + expect(fetchMock.calls(databaseApiRoute)[1][0]).toContain('keywordtest'); +}); + test('should show empty state if there are no options', async () => { fetchMock.reset(); fetchMock.get(databaseApiRoute, { result: [] }); diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx index 7b4afd9af05aa..0c0268db5c9e6 100644 --- a/superset-frontend/src/components/DatabaseSelector/index.tsx +++ b/superset-frontend/src/components/DatabaseSelector/index.tsx @@ -167,7 +167,7 @@ export default function DatabaseSelector({ }); const endpoint = `/api/v1/database/?q=${queryParams}`; return SupersetClient.get({ endpoint }).then(({ json }) => { - const { result } = json; + const { result, count } = json; if (getDbList) { getDbList(result); } @@ -189,7 +189,7 @@ export default function DatabaseSelector({ return { data: options, - totalCount: options.length, + totalCount: count ?? options.length, }; }); }, diff --git a/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx b/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx index 51cf104b271ca..6b0feb7268f7e 100644 --- a/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx +++ b/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx @@ -116,9 +116,7 @@ const LabeledErrorBoundInput = ({ {label} - {hasTooltip && ( - - )} + {hasTooltip && } alertIconStyles(theme, !!errorMessage)} diff --git a/superset-frontend/src/components/InfoTooltip/index.tsx b/superset-frontend/src/components/InfoTooltip/index.tsx index ba08393f18fc8..a3ad7aa21acb3 100644 --- a/superset-frontend/src/components/InfoTooltip/index.tsx +++ b/superset-frontend/src/components/InfoTooltip/index.tsx @@ -73,7 +73,7 @@ export default function InfoTooltip({ trigger = 'hover', overlayStyle = defaultOverlayStyle, bgColor = defaultColor, - viewBox = '0 -2 24 24', + viewBox = '0 -1 24 24', }: InfoTooltipProps) { return ( any; + onChange: (value: SortColumn[]) => void; options: Array; initialSort?: SortColumn[]; - pageIndex: number; - pageSize: number; } export const CardSortSelect = ({ initialSort, onChange, options, - pageIndex, - pageSize, }: CardViewSelectSortProps) => { const defaultSort = - (initialSort && options.find(({ id }) => id === initialSort[0].id)) || + (initialSort && + options.find( + ({ id, desc }) => + id === initialSort[0].id && desc === initialSort[0].desc, + )) || options[0]; const [value, setValue] = useState({ @@ -72,7 +72,7 @@ export const CardSortSelect = ({ desc: originalOption.desc, }, ]; - onChange({ pageIndex, pageSize, sortBy, filters: [] }); + onChange(sortBy); } }; @@ -82,7 +82,7 @@ export const CardSortSelect = ({ ariaLabel={t('Sort')} header={{t('Sort')}} labelInValue - onChange={(value: CardSortSelectOption) => handleOnChange(value)} + onChange={handleOnChange} options={formattedOptions} showSearch value={value} diff --git a/superset-frontend/src/components/ListView/ListView.test.tsx b/superset-frontend/src/components/ListView/ListView.test.tsx new file mode 100644 index 0000000000000..9f4da16140f2c --- /dev/null +++ b/superset-frontend/src/components/ListView/ListView.test.tsx @@ -0,0 +1,74 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { render, waitFor } from 'spec/helpers/testing-library'; +import ListView from './ListView'; + +const mockedProps = { + title: 'Data Table', + columns: [ + { + accessor: 'id', + Header: 'ID', + sortable: true, + }, + { + accessor: 'age', + Header: 'Age', + }, + { + accessor: 'name', + Header: 'Name', + }, + { + accessor: 'time', + Header: 'Time', + }, + ], + data: [ + { id: 1, name: 'data 1', age: 10, time: '2020-11-18T07:53:45.354Z' }, + { id: 2, name: 'data 2', age: 1, time: '2020-11-18T07:53:45.354Z' }, + ], + count: 2, + pageSize: 1, + loading: false, + refreshData: jest.fn(), + addSuccessToast: jest.fn(), + addDangerToast: jest.fn(), +}; + +test('redirects to first page when page index is invalid', async () => { + const fetchData = jest.fn(); + window.history.pushState({}, '', '/?pageIndex=9'); + render(, { + useRouter: true, + useQueryParams: true, + }); + await waitFor(() => { + expect(window.location.search).toEqual('?pageIndex=0'); + expect(fetchData).toBeCalledTimes(2); + expect(fetchData).toHaveBeenCalledWith( + expect.objectContaining({ pageIndex: 9 }), + ); + expect(fetchData).toHaveBeenCalledWith( + expect.objectContaining({ pageIndex: 0 }), + ); + }); + fetchData.mockClear(); +}); diff --git a/superset-frontend/src/components/ListView/ListView.tsx b/superset-frontend/src/components/ListView/ListView.tsx index 82cf6b187846a..0cdb4ba034d44 100644 --- a/superset-frontend/src/components/ListView/ListView.tsx +++ b/superset-frontend/src/components/ListView/ListView.tsx @@ -271,10 +271,11 @@ function ListView({ pageCount = 1, gotoPage, applyFilterValue, + setSortBy, selectedFlatRows, toggleAllRowsSelected, setViewMode, - state: { pageIndex, pageSize, internalFilters, viewMode }, + state: { pageIndex, pageSize, internalFilters, sortBy, viewMode }, query, } = useListViewState({ bulkSelectColumnConfig, @@ -321,6 +322,12 @@ function ListView({ if (!bulkSelectEnabled) toggleAllRowsSelected(false); }, [bulkSelectEnabled, toggleAllRowsSelected]); + useEffect(() => { + if (!loading && pageIndex > pageCount - 1 && pageCount > 0) { + gotoPage(0); + } + }, [gotoPage, loading, pageCount, pageIndex]); + return ( {allowBulkTagActions && ( @@ -350,11 +357,9 @@ function ListView({ )} {viewMode === 'card' && cardSortSelectOptions && ( setSortBy(value)} options={cardSortSelectOptions} - pageIndex={pageIndex} - pageSize={pageSize} /> )} @@ -464,7 +469,7 @@ function ListView({
gotoPage(p - 1)} hideFirstAndLastPageLinks /> diff --git a/superset-frontend/src/components/ListView/types.ts b/superset-frontend/src/components/ListView/types.ts index 0c1314f2637c6..a526d6870d4cc 100644 --- a/superset-frontend/src/components/ListView/types.ts +++ b/superset-frontend/src/components/ListView/types.ts @@ -23,8 +23,6 @@ export interface SortColumn { desc?: boolean; } -export type SortColumns = SortColumn[]; - export interface SelectOption { label: string; value: any; @@ -84,7 +82,7 @@ export interface FilterValue { export interface FetchDataConfig { pageIndex: number; pageSize: number; - sortBy: SortColumns; + sortBy: SortColumn[]; filters: FilterValue[]; } diff --git a/superset-frontend/src/components/ListView/utils.ts b/superset-frontend/src/components/ListView/utils.ts index 8a8c57cb6234e..ec80e4cff52d0 100644 --- a/superset-frontend/src/components/ListView/utils.ts +++ b/superset-frontend/src/components/ListView/utils.ts @@ -221,7 +221,7 @@ export function useListViewState({ query.sortColumn && query.sortOrder ? [{ id: query.sortColumn, desc: query.sortOrder === 'desc' }] : initialSort, - [query.sortColumn, query.sortOrder], + [initialSort, query.sortColumn, query.sortOrder], ); const initialState = { @@ -257,6 +257,7 @@ export function useListViewState({ pageCount, gotoPage, setAllFilters, + setSortBy, selectedFlatRows, toggleAllRowsSelected, state: { pageIndex, pageSize, sortBy, filters }, @@ -374,6 +375,7 @@ export function useListViewState({ rows, selectedFlatRows, setAllFilters, + setSortBy, state: { pageIndex, pageSize, sortBy, filters, internalFilters, viewMode }, toggleAllRowsSelected, applyFilterValue, diff --git a/superset-frontend/src/components/MessageToasts/getToastsFromPyFlashMessages.test.js b/superset-frontend/src/components/MessageToasts/getToastsFromPyFlashMessages.test.js deleted file mode 100644 index d19ac0c5d5ee2..0000000000000 --- a/superset-frontend/src/components/MessageToasts/getToastsFromPyFlashMessages.test.js +++ /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 { ToastType } from 'src/components/MessageToasts/types'; -import getToastsFromPyFlashMessages from 'src/components/MessageToasts/getToastsFromPyFlashMessages'; - -describe('getToastsFromPyFlashMessages', () => { - it('should return an info toast', () => { - const toast = getToastsFromPyFlashMessages([['info', 'info test']])[0]; - expect(toast).toMatchObject({ - toastType: ToastType.INFO, - text: 'info test', - }); - }); - - it('should return a success toast', () => { - const toast = getToastsFromPyFlashMessages([ - ['success', 'success test'], - ])[0]; - expect(toast).toMatchObject({ - toastType: ToastType.SUCCESS, - text: 'success test', - }); - }); - - it('should return a danger toast', () => { - const toast = getToastsFromPyFlashMessages([['danger', 'danger test']])[0]; - expect(toast).toMatchObject({ - toastType: ToastType.DANGER, - text: 'danger test', - }); - }); -}); diff --git a/superset-frontend/src/components/Select/AsyncSelect.test.tsx b/superset-frontend/src/components/Select/AsyncSelect.test.tsx index 0bb24b474a0cc..c2c60aa2bfdc1 100644 --- a/superset-frontend/src/components/Select/AsyncSelect.test.tsx +++ b/superset-frontend/src/components/Select/AsyncSelect.test.tsx @@ -939,6 +939,18 @@ test('pasting an existing option does not duplicate it in multiple mode', async expect(await findAllSelectOptions()).toHaveLength(4); }); +test('does not fire onChange if the same value is selected in single mode', async () => { + const onChange = jest.fn(); + render(); + const optionText = 'Emma'; + await open(); + expect(onChange).toHaveBeenCalledTimes(0); + userEvent.click(await findSelectOption(optionText)); + expect(onChange).toHaveBeenCalledTimes(1); + userEvent.click(await findSelectOption(optionText)); + expect(onChange).toHaveBeenCalledTimes(1); +}); + /* TODO: Add tests that require scroll interaction. Needs further investigation. - Fetches more data when scrolling and more data is available diff --git a/superset-frontend/src/components/Select/AsyncSelect.tsx b/superset-frontend/src/components/Select/AsyncSelect.tsx index d102af74833ee..e761151a0dac2 100644 --- a/superset-frontend/src/components/Select/AsyncSelect.tsx +++ b/superset-frontend/src/components/Select/AsyncSelect.tsx @@ -51,10 +51,12 @@ import { mapOptions, getOption, isObject, + isEqual as utilsIsEqual, } from './utils'; import { AsyncSelectProps, AsyncSelectRef, + RawValue, SelectOptionsPagePromise, SelectOptionsType, SelectOptionsTypePage, @@ -223,7 +225,16 @@ const AsyncSelect = forwardRef( const handleOnSelect: SelectProps['onSelect'] = (selectedItem, option) => { if (isSingleMode) { + // on select is fired in single value mode if the same value is selected + const valueChanged = !utilsIsEqual( + selectedItem, + selectValue as RawValue | AntdLabeledValue, + 'value', + ); setSelectValue(selectedItem); + if (valueChanged) { + fireOnChange(); + } } else { setSelectValue(previousState => { const array = ensureIsArray(previousState); @@ -237,8 +248,8 @@ const AsyncSelect = forwardRef( } return previousState; }); + fireOnChange(); } - fireOnChange(); onSelect?.(selectedItem, option); }; diff --git a/superset-frontend/src/components/Select/Select.test.tsx b/superset-frontend/src/components/Select/Select.test.tsx index 2910353295187..1daff06d4de77 100644 --- a/superset-frontend/src/components/Select/Select.test.tsx +++ b/superset-frontend/src/components/Select/Select.test.tsx @@ -1053,6 +1053,18 @@ test('pasting an existing option does not duplicate it in multiple mode', async expect(await findAllSelectOptions()).toHaveLength(4); }); +test('does not fire onChange if the same value is selected in single mode', async () => { + const onChange = jest.fn(); + render( = ({ chart: contentType === 'chart' ? currentAlert?.chart?.value : null, dashboard: contentType === 'dashboard' ? currentAlert?.dashboard?.value : null, + custom_width: isScreenshot ? currentAlert?.custom_width : undefined, database: currentAlert?.database?.value, owners: (currentAlert?.owners || []).map( owner => (owner as MetaObject).value || owner.id, @@ -890,6 +891,10 @@ const AlertReportModal: FunctionComponent = ({ updateAlertState(name, parsedValue); }; + const onCustomWidthChange = (value: number | null | undefined) => { + updateAlertState('custom_width', value); + }; + const onTimeoutVerifyChange = ( event: React.ChangeEvent, ) => { @@ -898,7 +903,7 @@ const AlertReportModal: FunctionComponent = ({ // Need to make sure grace period is not lower than TIMEOUT_MIN if (value === 0) { - updateAlertState(target.name, null); + updateAlertState(target.name, undefined); } else { updateAlertState( target.name, @@ -1358,7 +1363,7 @@ const AlertReportModal: FunctionComponent = ({ * updateAlertState('crontab', newVal)} />
{TRANSLATIONS.TIMEZONE_TEXT}
@@ -1504,14 +1509,16 @@ const AlertReportModal: FunctionComponent = ({ {TRANSLATIONS.CUSTOM_SCREENSHOT_WIDTH_TEXT}
-
diff --git a/superset-frontend/src/features/rls/RowLevelSecurityModal.tsx b/superset-frontend/src/features/rls/RowLevelSecurityModal.tsx index d14d48d0e51dc..9997de5da7e9b 100644 --- a/superset-frontend/src/features/rls/RowLevelSecurityModal.tsx +++ b/superset-frontend/src/features/rls/RowLevelSecurityModal.tsx @@ -28,26 +28,32 @@ import Modal from 'src/components/Modal'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import Icons from 'src/components/Icons'; import Select from 'src/components/Select/Select'; +import { TextArea } from 'src/components/Input'; import AsyncSelect from 'src/components/Select/AsyncSelect'; import rison from 'rison'; import { LabeledErrorBoundInput } from 'src/components/Form'; -import { noBottomMargin } from 'src/features/reports/ReportModal/styles'; import InfoTooltip from 'src/components/InfoTooltip'; import { useSingleViewResource } from 'src/views/CRUD/hooks'; -import { FilterOptions } from './constants'; +import { FILTER_OPTIONS } from './constants'; import { FilterType, RLSObject, RoleObject, TableObject } from './types'; +const noMargins = css` + margin: 0; + + .ant-input { + margin: 0; + } +`; + const StyledModal = styled(Modal)` max-width: 1200px; min-width: min-content; width: 100%; - .ant-modal-body { - overflow: initial; - } .ant-modal-footer { white-space: nowrap; } `; + const StyledIcon = (theme: SupersetTheme) => css` margin: auto ${theme.gridUnit * 2}px auto 0; color: ${theme.colors.grayscale.base}; @@ -59,9 +65,17 @@ const StyledSectionContainer = styled.div` padding: ${({ theme }) => `${theme.gridUnit * 3}px ${theme.gridUnit * 4}px ${theme.gridUnit * 2}px`}; - label { + label, + .control-label { + display: inline-block; font-size: ${({ theme }) => theme.typography.sizes.s}px; - color: ${({ theme }) => theme.colors.grayscale.light1}; + color: ${({ theme }) => theme.colors.grayscale.base}; + vertical-align: middle; + } + + .info-solid-small { + vertical-align: middle; + padding-bottom: ${({ theme }) => theme.gridUnit / 2}px; } `; @@ -69,7 +83,6 @@ const StyledInputContainer = styled.div` display: flex; flex-direction: column; margin: ${({ theme }) => theme.gridUnit}px; - margin-bottom: ${({ theme }) => theme.gridUnit * 4}px; .input-container { @@ -79,11 +92,6 @@ const StyledInputContainer = styled.div` > div { width: 100%; } - - label { - display: flex; - margin-right: ${({ theme }) => theme.gridUnit * 2}px; - } } input, @@ -91,17 +99,17 @@ const StyledInputContainer = styled.div` flex: 1 1 auto; } - textarea { - height: 100px; - resize: none; - } - .required { margin-left: ${({ theme }) => theme.gridUnit / 2}px; color: ${({ theme }) => theme.colors.error.base}; } `; +const StyledTextArea = styled(TextArea)` + resize: none; + margin-top: ${({ theme }) => theme.gridUnit}px; +`; + export interface RowLevelSecurityModalProps { rule: RLSObject | null; addSuccessToast: (msg: string) => void; @@ -144,23 +152,25 @@ function RowLevelSecurityModal(props: RowLevelSecurityModalProps) { addDangerToast, ); - // initialize - useEffect(() => { - if (!isEditMode) { - setCurrentRule({ ...DEAFULT_RULE }); - } else if (rule?.id !== null && !loading && !fetchError) { - fetchResource(rule.id as number); - } - }, [rule]); + const updateRuleState = (name: string, value: any) => { + setCurrentRule(currentRuleData => ({ + ...currentRuleData, + [name]: value, + })); + }; - useEffect(() => { - if (resource) { - setCurrentRule({ ...resource, id: rule?.id }); - const selectedTableAndRoles = getSelectedData(); - updateRuleState('tables', selectedTableAndRoles?.tables || []); - updateRuleState('roles', selectedTableAndRoles?.roles || []); + // * state validators * + const validate = () => { + if ( + currentRule?.name && + currentRule?.clause && + currentRule.tables?.length + ) { + setDisableSave(false); + } else { + setDisableSave(true); } - }, [resource]); + }; // find selected tables and roles const getSelectedData = useCallback(() => { @@ -191,6 +201,24 @@ function RowLevelSecurityModal(props: RowLevelSecurityModalProps) { return { tables, roles }; }, [resource?.tables, resource?.roles]); + // initialize + useEffect(() => { + if (!isEditMode) { + setCurrentRule({ ...DEAFULT_RULE }); + } else if (rule?.id !== null && !loading && !fetchError) { + fetchResource(rule.id as number); + } + }, [rule]); + + useEffect(() => { + if (resource) { + setCurrentRule({ ...resource, id: rule?.id }); + const selectedTableAndRoles = getSelectedData(); + updateRuleState('tables', selectedTableAndRoles?.tables || []); + updateRuleState('roles', selectedTableAndRoles?.roles || []); + } + }, [resource]); + // validate const currentRuleSafe = currentRule || {}; useEffect(() => { @@ -203,13 +231,6 @@ function RowLevelSecurityModal(props: RowLevelSecurityModalProps) { label: string; }; - const updateRuleState = (name: string, value: any) => { - setCurrentRule(currentRuleData => ({ - ...currentRuleData, - [name]: value, - })); - }; - const onTextChange = (target: HTMLInputElement | HTMLTextAreaElement) => { updateRuleState(target.name, target.value); }; @@ -307,19 +328,6 @@ function RowLevelSecurityModal(props: RowLevelSecurityModalProps) { [], ); - // * state validators * - const validate = () => { - if ( - currentRule?.name && - currentRule?.clause && - currentRule.tables?.length - ) { - setDisableSave(false); - } else { - setDisableSave(true); - } - }; - return ( onTextChange(target), }} - css={noBottomMargin} + css={noMargins} label={t('Rule Name')} data-test="rule-name-test" + tooltipText={t('The name of the rule must be unique')} + hasTooltip /> -
{t('Filter Type')}{' '} @@ -377,12 +386,11 @@ function RowLevelSecurityModal(props: RowLevelSecurityModalProps) { placeholder={t('Filter Type')} onChange={onFilterChange} value={currentRule?.filter_type} - options={FilterOptions} + options={FILTER_OPTIONS} data-test="rule-filter-type-test" />
-
{t('Datasets')} * @@ -433,7 +441,7 @@ function RowLevelSecurityModal(props: RowLevelSecurityModalProps) { onChange: ({ target }: { target: HTMLInputElement }) => onTextChange(target), }} - css={noBottomMargin} + css={noMargins} label={t('Group Key')} hasTooltip tooltipText={t( @@ -442,7 +450,6 @@ function RowLevelSecurityModal(props: RowLevelSecurityModalProps) { data-test="group-key-test" /> -
onTextChange(target), }} - css={noBottomMargin} + css={noMargins} label={t('Clause')} hasTooltip tooltipText={t( @@ -464,11 +471,11 @@ function RowLevelSecurityModal(props: RowLevelSecurityModalProps) { />
-
{t('Description')}
-