diff --git a/.pylintrc b/.pylintrc index 53a9b2ffd2f1a..49b3984d1943d 100644 --- a/.pylintrc +++ b/.pylintrc @@ -81,7 +81,7 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=long-builtin,dict-view-method,intern-builtin,suppressed-message,no-absolute-import,unpacking-in-except,apply-builtin,delslice-method,indexing-exception,old-raise-syntax,print-statement,cmp-builtin,reduce-builtin,useless-suppression,coerce-method,input-builtin,cmp-method,raw_input-builtin,nonzero-method,backtick,basestring-builtin,setslice-method,reload-builtin,oct-method,map-builtin-not-iterating,execfile-builtin,old-octal-literal,zip-builtin-not-iterating,buffer-builtin,getslice-method,metaclass-assignment,xrange-builtin,long-suffix,round-builtin,range-builtin-not-iterating,next-method-called,parameter-unpacking,unicode-builtin,unichr-builtin,import-star-module-level,raising-string,filter-builtin-not-iterating,using-cmp-argument,coerce-builtin,file-builtin,old-division,hex-method,missing-docstring,too-many-lines,ungrouped-imports,import-outside-toplevel,raise-missing-from,super-with-arguments,bad-option-value,too-few-public-methods +disable=long-builtin,dict-view-method,intern-builtin,suppressed-message,no-absolute-import,unpacking-in-except,apply-builtin,delslice-method,indexing-exception,old-raise-syntax,print-statement,cmp-builtin,reduce-builtin,useless-suppression,coerce-method,input-builtin,cmp-method,raw_input-builtin,nonzero-method,backtick,basestring-builtin,setslice-method,reload-builtin,oct-method,map-builtin-not-iterating,execfile-builtin,old-octal-literal,zip-builtin-not-iterating,buffer-builtin,getslice-method,metaclass-assignment,xrange-builtin,long-suffix,round-builtin,range-builtin-not-iterating,next-method-called,parameter-unpacking,unicode-builtin,unichr-builtin,import-star-module-level,raising-string,filter-builtin-not-iterating,using-cmp-argument,coerce-builtin,file-builtin,old-division,hex-method,missing-docstring,too-many-lines,ungrouped-imports,import-outside-toplevel,raise-missing-from,super-with-arguments,bad-option-value,too-few-public-methods,too-many-locals [REPORTS] diff --git a/Dockerfile b/Dockerfile index 9d6a61bef9b50..6ca89e889fd7d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -84,8 +84,8 @@ ENV LANG=C.UTF-8 \ SUPERSET_HOME="/app/superset_home" \ SUPERSET_PORT=8088 -RUN useradd --user-group --no-create-home --no-log-init --shell /bin/bash superset \ - && mkdir -p ${SUPERSET_HOME} ${PYTHONPATH} \ +RUN mkdir -p ${PYTHONPATH} \ + && useradd --user-group -d ${SUPERSET_HOME} -m --no-log-init --shell /bin/bash superset \ && apt-get update -y \ && apt-get install -y --no-install-recommends \ build-essential \ diff --git a/docker-compose.yml b/docker-compose.yml index 94300592093fe..8b94f70172227 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,7 @@ # limitations under the License. # x-superset-image: &superset-image apache/superset:latest-dev +x-superset-user: &superset-user root x-superset-depends-on: &superset-depends-on - db - redis @@ -55,7 +56,7 @@ services: restart: unless-stopped ports: - 8088:8088 - user: "root" + user: *superset-user depends_on: *superset-depends-on volumes: *superset-volumes environment: @@ -95,7 +96,7 @@ services: command: ["/app/docker/docker-init.sh"] env_file: docker/.env depends_on: *superset-depends-on - user: "root" + user: *superset-user volumes: *superset-volumes environment: CYPRESS_CONFIG: "${CYPRESS_CONFIG}" @@ -115,7 +116,7 @@ services: env_file: docker/.env restart: unless-stopped depends_on: *superset-depends-on - user: "root" + user: *superset-user volumes: *superset-volumes superset-worker-beat: @@ -125,7 +126,7 @@ services: env_file: docker/.env restart: unless-stopped depends_on: *superset-depends-on - user: "root" + user: *superset-user volumes: *superset-volumes superset-tests-worker: @@ -141,7 +142,7 @@ services: REDIS_HOST: localhost network_mode: host depends_on: *superset-depends-on - user: "root" + user: *superset-user volumes: *superset-volumes volumes: diff --git a/docker/docker-bootstrap.sh b/docker/docker-bootstrap.sh index b934640efb0c0..57163ccfe4c4c 100755 --- a/docker/docker-bootstrap.sh +++ b/docker/docker-bootstrap.sh @@ -41,7 +41,7 @@ if [[ "${1}" == "worker" ]]; then celery worker --app=superset.tasks.celery_app:app -Ofair -l INFO elif [[ "${1}" == "beat" ]]; then echo "Starting Celery beat..." - celery beat --app=superset.tasks.celery_app:app --pidfile /tmp/celerybeat.pid -l INFO + celery beat --app=superset.tasks.celery_app:app --pidfile /tmp/celerybeat.pid -l INFO -s "${SUPERSET_HOME}"/celerybeat-schedule elif [[ "${1}" == "app" ]]; then echo "Starting web app..." flask run -p 8088 --with-threads --reload --debugger --host=0.0.0.0 diff --git a/docs/installation.rst b/docs/installation.rst index 8d28bd0b811f7..edfedce43965d 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -433,7 +433,7 @@ For other strategies, check the `superset/tasks/cache.py` file. Caching Thumbnails ------------------ -This is an optional feature that can be turned on by activating it's feature flag on config: +This is an optional feature that can be turned on by activating its feature flag on config: .. code-block:: python @@ -972,7 +972,7 @@ environment variable: :: Event Logging ------------- -Superset by default logs special action event on it's database. These log can be accessed on the UI navigating to +Superset by default logs special action event on its database. These logs can be accessed on the UI navigating to "Security" -> "Action Log". You can freely customize these logs by implementing your own event log class. Example of a simple JSON to Stdout class:: @@ -1256,6 +1256,38 @@ in this dictionary are made available for users to use in their SQL. 'my_crazy_macro': lambda x: x*2, } +Default values for jinja templates can be specified via ``Parameters`` menu in the SQL Lab user interface. +In the UI you can assign a set of parameters as JSON + +.. code-block:: JSON + { + "my_table": "foo" + } + +The parameters become available in your SQL (example:SELECT * FROM {{ my_table }} ) by using Jinja templating syntax. +SQL Lab template parameters are stored with the dataset as TEMPLATE PARAMETERS. + +There is a special ``_filters`` parameter which can be used to test filters used in the jinja template. + +.. code-block:: JSON + { + "_filters": [ { + "col": "action_type", + "op": "IN", + "val": ["sell", "buy"] + } ] + } + +.. code-block:: python + SELECT action, count(*) as times + FROM logs + WHERE + action in ({{ "'" + "','".join(filter_values('action_type')) + "'" }}) + GROUP BY action + +Note ``_filters`` is not stored with the dataset. It's only used within the SQL Lab UI. + + Besides default Jinja templating, SQL lab also supports self-defined template processor by setting the ``CUSTOM_TEMPLATE_PROCESSORS`` in your superset configuration. The values in this dictionary overwrite the default Jinja template processors of the @@ -1326,7 +1358,7 @@ The available validators and names can be found in `sql_validators/`. **Scheduling queries** You can optionally allow your users to schedule queries directly in SQL Lab. -This is done by addding extra metadata to saved queries, which are then picked +This is done by adding extra metadata to saved queries, which are then picked up by an external scheduled (like [Apache Airflow](https://airflow.apache.org/)). To allow scheduled queries, add the following to your `config.py`: diff --git a/docs/src/pages/docs/Connecting to Databases/mysql.mdx b/docs/src/pages/docs/Connecting to Databases/mysql.mdx index 241eacee49dec..3db94f7bf21d5 100644 --- a/docs/src/pages/docs/Connecting to Databases/mysql.mdx +++ b/docs/src/pages/docs/Connecting to Databases/mysql.mdx @@ -8,12 +8,12 @@ version: 1 ## MySQL -The recommended connector library for MySQL is [mysql-connector-python](https://pypi.org/project/mysql-connector-python/). +The recommended connector library for MySQL is `[mysqlclient](https://pypi.org/project/mysqlclient/)`. Here's the connection string: ``` -mysql+mysqlconnector://{username}:{password}@{host}/{database} +mysql://{username}:{password}@{host}/{database} ``` Host: @@ -21,3 +21,9 @@ Host: - For On Prem: IP address or Host name - For Docker running in OSX: `docker.for.mac.host.internal` Port: `3306` by default + +One problem with `mysqlclient` is that it will fail to connect to newer MySQL databases using `caching_sha2_password` for authentication, since the plugin is not included in the client. In this case, you should use `[mysql-connector-python](https://pypi.org/project/mysql-connector-python/)` instead: + +``` +mysql+mysqlconnector://{username}:{password}@{host}/{database} +``` diff --git a/docs/src/pages/docs/installation/configuring.mdx b/docs/src/pages/docs/installation/configuring.mdx index c725bf5950311..53340a5c9cdad 100644 --- a/docs/src/pages/docs/installation/configuring.mdx +++ b/docs/src/pages/docs/installation/configuring.mdx @@ -134,7 +134,7 @@ OAUTH_PROVIDERS = [ 'access_token_headers':{ # Additional headers for calls to access_token_url 'Authorization': 'Basic Base64EncodedClientIdAndSecret' }, - 'base_url':'https://myAuthorizationServer/oauth2AuthorizationServer/', + 'api_base_url':'https://myAuthorizationServer/oauth2AuthorizationServer/', 'access_token_url':'https://myAuthorizationServer/oauth2AuthorizationServer/token', 'authorize_url':'https://myAuthorizationServer/oauth2AuthorizationServer/authorize' } diff --git a/helm/superset/Chart.yaml b/helm/superset/Chart.yaml index 4fd7f59cd8da6..d06eff1859fda 100644 --- a/helm/superset/Chart.yaml +++ b/helm/superset/Chart.yaml @@ -22,7 +22,7 @@ maintainers: - name: craig-rueda email: craig@craigrueda.com url: https://github.com/craig-rueda -version: 0.1.2 +version: 0.1.3 dependencies: - name: postgresql version: 10.2.0 diff --git a/helm/superset/templates/deployment-beat.yaml b/helm/superset/templates/deployment-beat.yaml index 128bc6d767dc6..0ec76da15e88c 100644 --- a/helm/superset/templates/deployment-beat.yaml +++ b/helm/superset/templates/deployment-beat.yaml @@ -96,6 +96,10 @@ spec: tolerations: {{ toYaml . | indent 8 }} {{- end }} +{{- if .Values.imagePullSecrets }} + imagePullSecrets: +{{ toYaml .Values.imagePullSecrets | indent 8 }} + {{- end }} volumes: - name: superset-config secret: diff --git a/helm/superset/templates/deployment-worker.yaml b/helm/superset/templates/deployment-worker.yaml index 4001e76e920a5..fe0ce20d8580f 100644 --- a/helm/superset/templates/deployment-worker.yaml +++ b/helm/superset/templates/deployment-worker.yaml @@ -94,6 +94,10 @@ spec: tolerations: {{ toYaml . | indent 8 }} {{- end }} +{{- if .Values.imagePullSecrets }} + imagePullSecrets: +{{ toYaml .Values.imagePullSecrets | indent 8 }} + {{- end }} volumes: - name: superset-config secret: diff --git a/helm/superset/templates/deployment.yaml b/helm/superset/templates/deployment.yaml index 2a611ca8f5bec..8e807daf15e82 100644 --- a/helm/superset/templates/deployment.yaml +++ b/helm/superset/templates/deployment.yaml @@ -106,6 +106,11 @@ spec: tolerations: {{ toYaml . | indent 8 }} {{- end }} +{{- if .Values.imagePullSecrets }} + imagePullSecrets: +{{ toYaml .Values.imagePullSecrets | indent 8 }} + {{- end }} + volumes: - name: superset-config secret: diff --git a/helm/superset/templates/init-job.yaml b/helm/superset/templates/init-job.yaml index 9a4530d3c8ee2..b3c4fd42f67a2 100644 --- a/helm/superset/templates/init-job.yaml +++ b/helm/superset/templates/init-job.yaml @@ -59,6 +59,10 @@ spec: command: {{ tpl (toJson .Values.init.command) . }} resources: {{ toYaml .Values.init.resources | indent 10 }} +{{- if .Values.imagePullSecrets }} + imagePullSecrets: +{{ toYaml .Values.imagePullSecrets | indent 8 }} + {{- end }} volumes: - name: superset-config secret: diff --git a/helm/superset/values.yaml b/helm/superset/values.yaml index 1a25419d16606..58e9faaef5fab 100644 --- a/helm/superset/values.yaml +++ b/helm/superset/values.yaml @@ -106,6 +106,9 @@ image: tag: latest pullPolicy: IfNotPresent +imagePullSecrets: [] + + service: type: ClusterIP port: 8088 diff --git a/requirements/base.txt b/requirements/base.txt index 47c234686adb2..0f433e0d66474 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -27,7 +27,7 @@ backoff==1.10.0 # via apache-superset billiard==3.6.3.0 # via celery -bleach==3.2.1 +bleach==3.3.0 # via apache-superset brotli==1.0.9 # via flask-compress @@ -56,7 +56,7 @@ cron-descriptor==1.2.24 # via apache-superset croniter==0.3.36 # via apache-superset -cryptography==3.2.1 +cryptography==3.3.2 # via apache-superset decorator==4.4.2 # via retry diff --git a/setup.py b/setup.py index bffd2eb44a4f1..30e4c5479f7ec 100644 --- a/setup.py +++ b/setup.py @@ -73,7 +73,7 @@ def get_git_sha(): "contextlib2", "croniter>=0.3.28", "cron-descriptor", - "cryptography>=3.2.1", + "cryptography>=3.3.2", "flask>=1.1.0, <2.0.0", "flask-appbuilder>=3.3.0, <4.0.0", "flask-caching", diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts index 6afe2c61900a8..375f14dacdd82 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts @@ -70,8 +70,8 @@ describe('Dashboard filter', () => { } expect(requestFilter).deep.eq({ col: 'region', - op: '==', - val: 'South Asia', + op: 'IN', + val: ['South Asia'], }); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.ts index d4ec42e0df3cf..85b8f51dee6c7 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.ts @@ -121,8 +121,8 @@ describe('Dashboard tabs', () => { const requestParams = JSON.parse(requestBody.form_data as string); expect(requestParams.extra_filters[0]).deep.eq({ col: 'region', - op: '==', - val: 'South Asia', + op: 'IN', + val: ['South Asia'], }); }); }); @@ -136,8 +136,8 @@ describe('Dashboard tabs', () => { const requestParams = JSON.parse(requestBody.form_data as string); expect(requestParams.extra_filters[0]).deep.eq({ col: 'region', - op: '==', - val: 'South Asia', + op: 'IN', + val: ['South Asia'], }); expect(requestParams.viz_type).eq(LINE_CHART.viz); }); @@ -150,8 +150,8 @@ describe('Dashboard tabs', () => { cy.wait('@v1ChartData').then(({ request }) => { expect(request.body.queries[0].filters[0]).deep.eq({ col: 'region', - op: '==', - val: 'South Asia', + op: 'IN', + val: ['South Asia'], }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts index 61bdd7c6d9260..caff4c03f6755 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts @@ -61,7 +61,6 @@ describe('dashboard filters card view', () => { cy.get('.Select__menu').contains('Published').click({ timeout: 5000 }); cy.get('[data-test="styled-card"]').should('have.length', 2); cy.get('[data-test="styled-card"]') - .first() .contains('USA Births Names') .should('be.visible'); cy.get('.Select__control').eq(1).click(); @@ -107,13 +106,12 @@ describe('dashboard filters list view', () => { cy.get('[data-test="table-row"]').should('not.exist'); }); - xit('should filter by published correctly', () => { + it('should filter by published correctly', () => { // filter by published cy.get('.Select__control').eq(2).click(); cy.get('.Select__menu').contains('Published').click(); cy.get('[data-test="table-row"]').should('have.length', 2); cy.get('[data-test="table-row"]') - .first() .contains('USA Births Names') .should('be.visible'); cy.get('.Select__control').eq(2).click(); diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts index e8c1542300113..26660bc5fa677 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts @@ -29,7 +29,7 @@ describe('Visualization > Line', () => { it('should show validator error when no metric', () => { const formData = { ...LINE_CHART_DEFAULTS, metrics: [] }; cy.visitChartByParams(JSON.stringify(formData)); - cy.get('.ant-alert-warning').contains(`"Metrics" cannot be empty`); + cy.get('.ant-alert-warning').contains(`Metrics: cannot be empty`); }); it('should preload mathjs', () => { @@ -43,7 +43,7 @@ describe('Visualization > Line', () => { it('should not show validator error when metric added', () => { const formData = { ...LINE_CHART_DEFAULTS, metrics: [] }; cy.visitChartByParams(JSON.stringify(formData)); - cy.get('.ant-alert-warning').contains(`"Metrics" cannot be empty`); + cy.get('.ant-alert-warning').contains(`Metrics: cannot be empty`); cy.get('.text-danger').contains('Metrics'); cy.get('[data-test=metrics]') @@ -62,6 +62,8 @@ describe('Visualization > Line', () => { }); it('should allow negative values in Y bounds', () => { + const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] }; + cy.visitChartByParams(JSON.stringify(formData)); cy.get('#controlSections-tab-display').click(); cy.get('span').contains('Y Axis Bounds').scrollIntoView(); cy.get('input[placeholder="Min"]').type('-0.1', { delay: 100 }); diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 3cb402ae5d764..2649abee8eb27 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -15,35 +15,35 @@ "@emotion/babel-preset-css-prop": "^11.2.0", "@emotion/cache": "^11.1.3", "@emotion/react": "^11.1.5", - "@superset-ui/chart-controls": "^0.17.50", - "@superset-ui/core": "^0.17.50", - "@superset-ui/legacy-plugin-chart-calendar": "^0.17.50", - "@superset-ui/legacy-plugin-chart-chord": "^0.17.50", - "@superset-ui/legacy-plugin-chart-country-map": "^0.17.50", - "@superset-ui/legacy-plugin-chart-event-flow": "^0.17.50", - "@superset-ui/legacy-plugin-chart-force-directed": "^0.17.50", - "@superset-ui/legacy-plugin-chart-heatmap": "^0.17.50", - "@superset-ui/legacy-plugin-chart-histogram": "^0.17.50", - "@superset-ui/legacy-plugin-chart-horizon": "^0.17.50", - "@superset-ui/legacy-plugin-chart-map-box": "^0.17.50", - "@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.50", - "@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.50", - "@superset-ui/legacy-plugin-chart-partition": "^0.17.50", - "@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.50", - "@superset-ui/legacy-plugin-chart-rose": "^0.17.50", - "@superset-ui/legacy-plugin-chart-sankey": "^0.17.50", - "@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.50", - "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.50", - "@superset-ui/legacy-plugin-chart-treemap": "^0.17.50", - "@superset-ui/legacy-plugin-chart-world-map": "^0.17.50", - "@superset-ui/legacy-preset-chart-big-number": "^0.17.50", - "@superset-ui/legacy-preset-chart-deckgl": "^0.4.6", - "@superset-ui/legacy-preset-chart-nvd3": "^0.17.50", - "@superset-ui/plugin-chart-echarts": "^0.17.50", - "@superset-ui/plugin-chart-pivot-table": "^0.17.50", - "@superset-ui/plugin-chart-table": "^0.17.50", - "@superset-ui/plugin-chart-word-cloud": "^0.17.50", - "@superset-ui/preset-chart-xy": "^0.17.50", + "@superset-ui/chart-controls": "^0.17.53", + "@superset-ui/core": "^0.17.53", + "@superset-ui/legacy-plugin-chart-calendar": "^0.17.53", + "@superset-ui/legacy-plugin-chart-chord": "^0.17.53", + "@superset-ui/legacy-plugin-chart-country-map": "^0.17.53", + "@superset-ui/legacy-plugin-chart-event-flow": "^0.17.53", + "@superset-ui/legacy-plugin-chart-force-directed": "^0.17.53", + "@superset-ui/legacy-plugin-chart-heatmap": "^0.17.53", + "@superset-ui/legacy-plugin-chart-histogram": "^0.17.53", + "@superset-ui/legacy-plugin-chart-horizon": "^0.17.53", + "@superset-ui/legacy-plugin-chart-map-box": "^0.17.53", + "@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.53", + "@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.53", + "@superset-ui/legacy-plugin-chart-partition": "^0.17.53", + "@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.53", + "@superset-ui/legacy-plugin-chart-rose": "^0.17.53", + "@superset-ui/legacy-plugin-chart-sankey": "^0.17.53", + "@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.53", + "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.53", + "@superset-ui/legacy-plugin-chart-treemap": "^0.17.53", + "@superset-ui/legacy-plugin-chart-world-map": "^0.17.53", + "@superset-ui/legacy-preset-chart-big-number": "^0.17.53", + "@superset-ui/legacy-preset-chart-deckgl": "^0.4.7", + "@superset-ui/legacy-preset-chart-nvd3": "^0.17.53", + "@superset-ui/plugin-chart-echarts": "^0.17.53", + "@superset-ui/plugin-chart-pivot-table": "^0.17.53", + "@superset-ui/plugin-chart-table": "^0.17.53", + "@superset-ui/plugin-chart-word-cloud": "^0.17.53", + "@superset-ui/preset-chart-xy": "^0.17.53", "@vx/responsive": "^0.0.195", "abortcontroller-polyfill": "^1.1.9", "antd": "^4.9.4", @@ -102,6 +102,7 @@ "react-markdown": "^4.3.1", "react-redux": "^7.2.0", "react-resize-detector": "^6.0.1-rc.1", + "react-reverse-portal": "^2.0.1", "react-router-dom": "^5.1.2", "react-search-input": "^0.11.3", "react-select": "^3.1.0", @@ -14102,11 +14103,11 @@ } }, "node_modules/@superset-ui/chart-controls": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/chart-controls/-/chart-controls-0.17.50.tgz", - "integrity": "sha512-VVX8YxwYDcaD6pxfcxjDvwVA9pr34rzINNYYmumY3gCyWkfUCMs2oB11naavAbXqDOx93pD9sSfkR8GUEACahQ==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/chart-controls/-/chart-controls-0.17.53.tgz", + "integrity": "sha512-PjIDka4/lUwXUNEGjkQOIMwVWF2WfknqM6pKFNDPO0/nG4S4faQk96z/ABOXp8GYwIbBshnmmbmW4TCrCQ10Xw==", "dependencies": { - "@superset-ui/core": "0.17.50", + "@superset-ui/core": "0.17.53", "lodash": "^4.17.15", "prop-types": "^15.7.2" }, @@ -14118,9 +14119,9 @@ } }, "node_modules/@superset-ui/core": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/core/-/core-0.17.50.tgz", - "integrity": "sha512-YWCWZOHqsvXjzIGG+gKLJESsoSaobGcvIUQyQ+RN9nmqFJezBIlHenbsDVnn7eHN1jMOBUYTwmv5p9AojLslRw==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/core/-/core-0.17.53.tgz", + "integrity": "sha512-2bIRrK3Y+4ZSNu6drc1EzHTq6fO3aWfdjCh43ytju88nlADHheQXgwxEKnmjzI141qxiVL2+oSL2kC6pSTkW8A==", "dependencies": { "@babel/runtime": "^7.1.2", "@emotion/cache": "^11.1.3", @@ -14247,12 +14248,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-calendar": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-calendar/-/legacy-plugin-chart-calendar-0.17.50.tgz", - "integrity": "sha512-jLIYTStx04Jd2jZv7u8FZ9u+3Zf0bd/c1GjToG2w4VnbDc73eTEqiMhyJPGlaZuKABWP2pigEanmbpR0OTAD/g==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-calendar/-/legacy-plugin-chart-calendar-0.17.53.tgz", + "integrity": "sha512-NLevYzzhQyRgP+vdEfhJyDxJIBbGM/bJTJfFw1iRllny3WQax6iU/X5hUw/iWZqruVNkwSnUA39+EGcjU1aIjg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3-array": "^2.0.3", "d3-selection": "^1.4.0", "d3-tip": "^0.9.1", @@ -14271,24 +14272,24 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-chord": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-chord/-/legacy-plugin-chart-chord-0.17.50.tgz", - "integrity": "sha512-XUK6LnUBuhYAHsyqGId80a7f+vzXgVuiZfbFTRJy4M/uPNdIBKfxX1t4kKudHIlqKNoSV2pFIVwh+4h4KpmadQ==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-chord/-/legacy-plugin-chart-chord-0.17.53.tgz", + "integrity": "sha512-a3Y8b/1nSuFvzEzUDTVVmad5/YjTBhz0qU2rcVGrdKp2kzuSVXVVljdN7KVisDUNHhYqrttLM8RQrqGw9f7x1A==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "prop-types": "^15.6.2", "react": "^16.13.1" } }, "node_modules/@superset-ui/legacy-plugin-chart-country-map": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-country-map/-/legacy-plugin-chart-country-map-0.17.50.tgz", - "integrity": "sha512-toxr09cCUI4Wari215323T8PL5YddCtnvliKVRA4+8UEEU9bnh+gQDIh++UqXq51dAR63czyr4kmxbu//JLDEg==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-country-map/-/legacy-plugin-chart-country-map-0.17.53.tgz", + "integrity": "sha512-zTImQdeBT8raXnxafBIHvaVqOqKoECfyDwgFlPKhs4M7EXPG7U8/VLg0Oi2dCA7/SFZA/ASrJwc/KxW399vJhw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "d3-array": "^2.0.3", "prop-types": "^15.6.2" @@ -14303,13 +14304,13 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-event-flow": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-event-flow/-/legacy-plugin-chart-event-flow-0.17.50.tgz", - "integrity": "sha512-6Zq8CmIMCnqgcJJ8XSqWIexTZBbUG6lZto9isVnxBXLKiGAau6vMOLlWPZjyWPJETProVMnAc+CQm+YRhLI1TQ==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-event-flow/-/legacy-plugin-chart-event-flow-0.17.53.tgz", + "integrity": "sha512-QYL0Feyfu7ZH1GeQ9sfEaEgnW2IQG93sJnM29NO53CjSvdbbZItfU9v6xVnAo6jMwcam7JLNYRtuIPgJevNThw==", "dependencies": { "@data-ui/event-flow": "^0.0.84", - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "prop-types": "^15.6.2" }, "peerDependencies": { @@ -14317,12 +14318,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-force-directed": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-force-directed/-/legacy-plugin-chart-force-directed-0.17.50.tgz", - "integrity": "sha512-4KvRGdA2974MekvZ87ei/H5rP6MQooHB4PndLriRqqwtfwNs7LDlN3o/SRagKFj/8xvTxrZfprF0Kt+TO6Dk6A==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-force-directed/-/legacy-plugin-chart-force-directed-0.17.53.tgz", + "integrity": "sha512-F7hkrBxC7EWrClQ1jb7anzj1SmIjqXVMz2JKhzwEUk++Tafnn0mrB7Yo51u3twFFOY5bwn+KcI1NObzBRkXguQ==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "prop-types": "^15.7.2" }, @@ -14331,12 +14332,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-heatmap": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-heatmap/-/legacy-plugin-chart-heatmap-0.17.50.tgz", - "integrity": "sha512-B/qt/z2ISVkiBRPoGDo4TVsur7QgFG3OKtIzjx6k+8KoRC1oWqeA2zTJAi55lp0bch5Mo4iIUwoyEY45T9nzOA==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-heatmap/-/legacy-plugin-chart-heatmap-0.17.53.tgz", + "integrity": "sha512-NXx/E3AiTxkL+qwaj8B0IDrhWo6P5u5EuXXx1xaWqMTH18YomyeA9l4NBPwsjCfhAMqrEeT0hzeY2/WSoPq5KQ==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "d3-svg-legend": "^1.x", "d3-tip": "^0.9.1", @@ -14344,14 +14345,14 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-histogram": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-histogram/-/legacy-plugin-chart-histogram-0.17.50.tgz", - "integrity": "sha512-AO2VbdJERQfSAVTsCVOkCjkzaHOzFhTgyUQpuCNwaMt6sV9yzKR/G3QHYxrfzTT/2DxDEjAX+uV26b821VFA8A==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-histogram/-/legacy-plugin-chart-histogram-0.17.53.tgz", + "integrity": "sha512-EQ/VvG+qCec+IqnwYHA90iHAjkhnPNGkKbTuKlsRyL3ONfxg3n6L4EQOlAA0HvELKkFAZXBxh8TA8Qc3j+g4Fw==", "dependencies": { "@data-ui/histogram": "^0.0.84", "@data-ui/theme": "^0.0.84", - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "@vx/legend": "^0.0.198", "@vx/responsive": "^0.0.199", "@vx/scale": "^0.0.197", @@ -14420,12 +14421,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-horizon": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-horizon/-/legacy-plugin-chart-horizon-0.17.50.tgz", - "integrity": "sha512-rfWJtCIITXqkYMW5Ue0MniqKYSQILtdVq8KDAtX2h5KuttDSi/2/ahBdwUmAs4rcjeSHH2FCaJONMAfAV0u1eA==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-horizon/-/legacy-plugin-chart-horizon-0.17.53.tgz", + "integrity": "sha512-LsM4HOuOkiabRNxMUjjietbFx99admne59Mm5zQdsRPNEpN/EKEWu8R4G4crSSqxxzD9KVnveRPE7OD0n91k/A==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3-array": "^2.0.3", "d3-scale": "^3.0.1", "prop-types": "^15.6.2" @@ -14455,12 +14456,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-map-box": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-map-box/-/legacy-plugin-chart-map-box-0.17.50.tgz", - "integrity": "sha512-9ksPlfBRQHqWuoktnpnRtR0N7l8FbZ0caBvK9I5+zuWDv8/rc5sunjkmDmAAJg3GSWfn7NzNS0spSk6YsfmKOQ==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-map-box/-/legacy-plugin-chart-map-box-0.17.53.tgz", + "integrity": "sha512-JuM77arnxECuSiHkdLMry4JruuVTAfTKTtR8F4qGOpiYiXzGEv4K+y12eqBe1o94ckJF43Esz9e1fdPLDkjqTw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "immutable": "^3.8.2", "mapbox-gl": "^0.53.0", "prop-types": "^15.6.2", @@ -14481,12 +14482,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-paired-t-test": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-paired-t-test/-/legacy-plugin-chart-paired-t-test-0.17.50.tgz", - "integrity": "sha512-20jRj92fsGm+fRzeQ8WHP4iFdczO9wMg9jok9OWtZHGUW0Sa3+YpbCW48of9qnqqf1Um1rqZu6PdmgKwevz1zg==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-paired-t-test/-/legacy-plugin-chart-paired-t-test-0.17.53.tgz", + "integrity": "sha512-QkRVm0XGoOxqOX0nRvHnGon2gG8MmV+dbBBpmPkmspxCWKrn183Wzq5SiMlM4vgo2HaroWUIPuBgLBd7rYZtGw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "distributions": "^1.0.0", "prop-types": "^15.6.2", "reactable": "^1.1.0" @@ -14496,12 +14497,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-parallel-coordinates": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-parallel-coordinates/-/legacy-plugin-chart-parallel-coordinates-0.17.50.tgz", - "integrity": "sha512-fo8ASuix28TqTPNlVTBWXPXBo99sVpxXaCkXpb4cSnO6F6V6B9Kv9vSKIfV6KZL6ul1wQTe/xLjt/lyyX5HjHg==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-parallel-coordinates/-/legacy-plugin-chart-parallel-coordinates-0.17.53.tgz", + "integrity": "sha512-NcwuEd+rXfmwPshPby0jEgnJnbYfKruM7l0Hb3lIw6iMTc1IV21d1CMftQPvYYdwagam0FapBO2YcSvnvj2rDw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "prop-types": "^15.7.2" }, @@ -14510,12 +14511,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-partition": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-partition/-/legacy-plugin-chart-partition-0.17.50.tgz", - "integrity": "sha512-cHwGtxftPk+j1BowOL12fTuM+eAWbAbfxGF4t+W0X9/ZX5KFpHMYR0cd836nPgfhTtO9sSc4/W34dP0QIQA9hQ==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-partition/-/legacy-plugin-chart-partition-0.17.53.tgz", + "integrity": "sha512-CTzKjaKCdT/+bFlXUDD4nXC2CO7mXmIPJ2K/M94rY2G2gdAWRZJ1i2HlcvTP+RY/AItzZm3C+E7hYdAQ6toBkA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "d3-hierarchy": "^1.1.8", "prop-types": "^15.6.2" @@ -14525,24 +14526,24 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-pivot-table": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.17.50.tgz", - "integrity": "sha512-KolczfBy36AnXO445UF8uUDTb9enw9qy7wURr7RcMeBzLG0xN0P8vxEG/OU4NQ3MbbBBi7lYbTuTEYdZ/5wQgw==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.17.53.tgz", + "integrity": "sha512-bk7mttnZFGgGmWCfj0kO++65XsMNyQJch0dgfRRnLVTlSnY89/kGqszTKybbCZhsbx4T5bJ+bn6hZKAGH+FnUA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "datatables.net-bs": "^1.10.15", "prop-types": "^15.6.2" } }, "node_modules/@superset-ui/legacy-plugin-chart-rose": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.17.50.tgz", - "integrity": "sha512-DW8FKdR/dXlVsavxARnQZTB+hZ7B/C8Kwh+Kwa8Ji+qCJ5p30cH0xGQ9oX5Lgc15L01MIew4sLYaFdE6i41DCA==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.17.53.tgz", + "integrity": "sha512-ppvQuKAS0rMhniKenLXSKczmAsHX4igYc0bVZAvfFDmLNW3tnlmivL+zYSw/sQ9PAhjMGDbTBlSio1oJ+91wiA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "nvd3": "1.8.6", "prop-types": "^15.6.2" @@ -14552,12 +14553,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-sankey": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey/-/legacy-plugin-chart-sankey-0.17.50.tgz", - "integrity": "sha512-d4kPCsY7nLZWhJDU1oXOC9+jwFoBWGvCI5n+GldJyhiDwxrp9+SBTZCx3ubmhgeI1HETeAD7C99DQ/neT5ttAA==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey/-/legacy-plugin-chart-sankey-0.17.53.tgz", + "integrity": "sha512-3tvMghg5WUAq40su8cZrjJHoc/TsK1WWx6UFu+j2mPOh/BJJZb8wh7A63X82ubLdyzEqdjxsEs9pzZWzs7kUHw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "d3-sankey": "^0.4.2", "prop-types": "^15.6.2" @@ -14567,47 +14568,47 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-sankey-loop": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey-loop/-/legacy-plugin-chart-sankey-loop-0.17.50.tgz", - "integrity": "sha512-AMgrl2Te24H5VoveHOjeHyhaI6tQtKo3EGuMb+RAHcgaBv1YRSaMx5LsR90qJtXlxMK1t4kRwG7mhqlE4kEP4Q==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey-loop/-/legacy-plugin-chart-sankey-loop-0.17.53.tgz", + "integrity": "sha512-60aGflqOi5+XDE3BR/p+Pw0xVp7OHsjwroX77CwkwBtFkw1AFVWczaTJH6CYeeCJZXCLYjrbc5OFMuaxIJ+j+Q==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3-sankey-diagram": "^0.7.3", "d3-selection": "^1.4.0", "prop-types": "^15.6.2" } }, "node_modules/@superset-ui/legacy-plugin-chart-sunburst": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sunburst/-/legacy-plugin-chart-sunburst-0.17.50.tgz", - "integrity": "sha512-ZFN4s74tVtxMbHV6rGylup1tWbI3nwOMqFccuP9m/Nk78B2IMuU2ljZxE/2Zd87gbpg5NOH1442yKcbqyYt0kw==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sunburst/-/legacy-plugin-chart-sunburst-0.17.53.tgz", + "integrity": "sha512-t0z7XPsDtDpnZ+fIpn57w9Vi3oWQ7ximDdjmag1WGhC6+dwR3XxEpNcicI6P6xfNX078RT8Iz89PZQBtagAAkA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "prop-types": "^15.6.2" } }, "node_modules/@superset-ui/legacy-plugin-chart-treemap": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.17.50.tgz", - "integrity": "sha512-1W9kH/2rcpo6bZNM4A00mHdSd8BCFuatf7Z45yHGwUnl1monnNcam0d5YxLMgR4NZMYCW76S5ltRPY+2+/weGg==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.17.53.tgz", + "integrity": "sha512-LV16Qwiz7ahfhCmuWIGk6f54KpdRJDAyLtr/ifFi8a2AcoG27Lf7hZZ3mCI9Jl5X6c7LLBmvAHfxdbBnLGa8+g==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3-hierarchy": "^1.1.8", "d3-selection": "^1.4.0", "prop-types": "^15.6.2" } }, "node_modules/@superset-ui/legacy-plugin-chart-world-map": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.17.50.tgz", - "integrity": "sha512-HIomXGPRxI3sejPhW/ktMI673Qd47VrcwkNjsjJSlz8MrlwAMGWNynEwEYAaTmKbm16hoesNN2JuZSVGBAHX4w==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.17.53.tgz", + "integrity": "sha512-gnDBTyWPctqucyQzAObH6N+3f9GUQq9qpQ4cNbtvpIoVgXowYA5Q5dIfXBPnq525t78o3eiWqclTYf2Xcd62Kw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "d3-array": "^2.4.0", "d3-color": "^1.4.1", @@ -14632,13 +14633,13 @@ "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" }, "node_modules/@superset-ui/legacy-preset-chart-big-number": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.50.tgz", - "integrity": "sha512-5ibPRV7LMzhUvFQ1WQgeBrcCaj8vmEk2Ocnuk+Kxk5cFFeOjF6Y/x2XbKz1PBwK1KTB4qC9OT3O6vJcTWHJjLQ==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.53.tgz", + "integrity": "sha512-HUlE6IZUjFvPMiXCj1cdRiR4avFLVhT5qwIQZk1l30kycl8/73rTm37Y/syBfZMPrfCrIW3nyReqfcnAaNqw9g==", "dependencies": { "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "@types/d3-color": "^1.2.2", "@types/shortid": "^0.0.29", "d3-color": "^1.2.3", @@ -14649,9 +14650,9 @@ } }, "node_modules/@superset-ui/legacy-preset-chart-deckgl": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-deckgl/-/legacy-preset-chart-deckgl-0.4.6.tgz", - "integrity": "sha512-xXGNj7WQHMA+QpeiHMrinwWhOwskD9ucXoe10AfFFgar9TwvCE6wpgRwnoyF0hjoaXnMqpYyFbzlucCf3WSfVQ==", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-deckgl/-/legacy-preset-chart-deckgl-0.4.7.tgz", + "integrity": "sha512-TaAX1PlZ5DhsNelgoOjCfPBlFtHZDFcozJEIAV2qXzXUo6rfIgskqIq4X3VbMuYnngZw5of4hAtOH1+Tgv+Wmw==", "dependencies": { "@math.gl/web-mercator": "^3.2.2", "@types/d3-array": "^2.0.0", @@ -14671,16 +14672,21 @@ "underscore": "^1.8.3", "urijs": "^1.18.10", "xss": "^1.0.6" + }, + "peerDependencies": { + "@superset-ui/chart-controls": "^0.17.12", + "@superset-ui/core": "^0.17.11", + "react": "^15 || ^16" } }, "node_modules/@superset-ui/legacy-preset-chart-nvd3": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.17.50.tgz", - "integrity": "sha512-jy2c37BXMQnSUc+pE9ZhiNiPNQHxweO+RD+gQcyF8DJ6Dn+woPE2DCfqjAdfVpUKuI4Y/FOM2lFlZT4xwGzsiA==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.17.53.tgz", + "integrity": "sha512-wTbQRCZDrnb16tLJzXYbIiCFbHddRJ3fo5DKsbv6MFNrfOLWWx1SjAZ5C60e57u33XpKdTE5jGpEuGdq7BZ55w==", "dependencies": { "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "d3-tip": "^0.9.1", "dompurify": "^2.0.6", @@ -14697,12 +14703,12 @@ } }, "node_modules/@superset-ui/plugin-chart-echarts": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-echarts/-/plugin-chart-echarts-0.17.50.tgz", - "integrity": "sha512-Og5QRwmYZ66zyUjBsBPBU9JzL1zTtgsyS/opGhMDCBGBJbWmAPi8j6kSO2sVSzrexoTYWtPbOO261uMKzuGZ0w==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-echarts/-/plugin-chart-echarts-0.17.53.tgz", + "integrity": "sha512-XXKqhr2CwZfi02qW55d9SQnNmdewTsAJT6xePBjci0SXAZRmi/T8vRbq2OCDJ7mQ0de7kjVBydAuOEEU/Y554A==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "@types/mathjs": "^6.0.7", "d3-array": "^1.2.0", "echarts": "^5.1.1", @@ -14714,25 +14720,25 @@ } }, "node_modules/@superset-ui/plugin-chart-pivot-table": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-pivot-table/-/plugin-chart-pivot-table-0.17.50.tgz", - "integrity": "sha512-WEa5lgJ4LbPb2v0MdHcDbDpnSN3khGmjvo2UV4G3AiJ6A47DLje8fhiYHkFhW7HfarozROYrwtcI/Rab6DYEyw==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-pivot-table/-/plugin-chart-pivot-table-0.17.53.tgz", + "integrity": "sha512-18CTaM1sRgK5laFwHlKV+1A7+l9YWwPAvb7XrMjS8CQq0T2aEqNSQm7KWByG+LEj2x86idM8gaWghNes27yVtQ==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", - "@superset-ui/react-pivottable": "^0.12.6" + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", + "@superset-ui/react-pivottable": "^0.12.8" }, "peerDependencies": { "react": "^16.13.1" } }, "node_modules/@superset-ui/plugin-chart-table": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.17.50.tgz", - "integrity": "sha512-L8z9dm/0qPkvo4F8+lLb6yF0x2e7MaRxkZO9RGC0A9HYiMWEUc2Bg9Z+/QmLV6B9mA8qgYQbAeiglfBKC9h7nQ==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.17.53.tgz", + "integrity": "sha512-PSeL/zQSTvQyztjUMMm4U4G6oEM3xk3wkC4HTpuLEpjQ7qyGme39M1JeCGvNG4pPZRm0nO4pU+0U/36oR0lAjw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "@types/d3-array": "^2.9.0", "@types/react-table": "^7.0.29", "d3-array": "^2.4.0", @@ -14758,12 +14764,12 @@ } }, "node_modules/@superset-ui/plugin-chart-word-cloud": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-word-cloud/-/plugin-chart-word-cloud-0.17.50.tgz", - "integrity": "sha512-IudEfIEy4JxJhmSrn0nctI4pIJY/KORi9FrT2ZiBRWTh1SmFhZNM2d9vQixsgXq1+bHN9J3JpZ3G0+4sFOtwsw==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-word-cloud/-/plugin-chart-word-cloud-0.17.53.tgz", + "integrity": "sha512-lQTr9UpeoGgX1B0SkjrvtL0zjgYVoJbm6RVv8ELG+efCG1oYAoIVgw2sahJI4zLqNiHcNeWqHUcu7NK06uc4mA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "@types/d3-cloud": "^1.2.1", "@types/d3-scale": "^2.0.2", "d3-cloud": "^1.2.5", @@ -14796,14 +14802,14 @@ } }, "node_modules/@superset-ui/preset-chart-xy": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/preset-chart-xy/-/preset-chart-xy-0.17.50.tgz", - "integrity": "sha512-pUhfI1aojKKDcrFB0aInkL7WmLlKdcOBhddsJ4+c5UfxpQZGdQ9VR9i0PPyvs8dbF4bRWyJNs31h9RHt7NOVSw==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/preset-chart-xy/-/preset-chart-xy-0.17.53.tgz", + "integrity": "sha512-nmqif4Zd7Tdx4hLoDiiRiNFUFn1kliumjp9RQK68eMaefWcl1vTMT7nPmyFvgUH5390HJygpC3up50+j5Bngkg==", "dependencies": { "@data-ui/theme": "^0.0.84", "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "@vx/axis": "^0.0.198", "@vx/legend": "^0.0.198", "@vx/scale": "^0.0.197", @@ -14923,9 +14929,9 @@ } }, "node_modules/@superset-ui/react-pivottable": { - "version": "0.12.6", - "resolved": "https://registry.npmjs.org/@superset-ui/react-pivottable/-/react-pivottable-0.12.6.tgz", - "integrity": "sha512-2+81WL4ocv4VFzgkj3wOBcEgejnJfsJ2D08kMvFfeBt6fhqC35nkendeZMAjl4bFBmzSJIFS6H+agjoeOUyq5A==", + "version": "0.12.8", + "resolved": "https://registry.npmjs.org/@superset-ui/react-pivottable/-/react-pivottable-0.12.8.tgz", + "integrity": "sha512-7DRxX/w1uSQE1pibSe64t1o+fmiP7ZWT2FJkjK510bSJm8NUIPCXtmpK+NKtNZuCteE9sqE7bQxd54SSq2xWKw==", "dependencies": { "immutability-helper": "^3.1.1", "prop-types": "^15.7.2", @@ -44732,6 +44738,15 @@ "resize-observer-polyfill": "^1.5.1" } }, + "node_modules/react-reverse-portal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/react-reverse-portal/-/react-reverse-portal-2.0.1.tgz", + "integrity": "sha512-sj/D9nSHspqV8i8hWkTSZ5Ohnrqk2A5fkDKw4Xe/zV4OfF1UYwmbzrxLdmNRdKkWgQwnXIxaa2E3FC7QYdZAeA==", + "peerDependencies": { + "react": "^16.0.0", + "react-dom": "^16.0.0" + } + }, "node_modules/react-router": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz", @@ -66270,19 +66285,19 @@ } }, "@superset-ui/chart-controls": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/chart-controls/-/chart-controls-0.17.50.tgz", - "integrity": "sha512-VVX8YxwYDcaD6pxfcxjDvwVA9pr34rzINNYYmumY3gCyWkfUCMs2oB11naavAbXqDOx93pD9sSfkR8GUEACahQ==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/chart-controls/-/chart-controls-0.17.53.tgz", + "integrity": "sha512-PjIDka4/lUwXUNEGjkQOIMwVWF2WfknqM6pKFNDPO0/nG4S4faQk96z/ABOXp8GYwIbBshnmmbmW4TCrCQ10Xw==", "requires": { - "@superset-ui/core": "0.17.50", + "@superset-ui/core": "0.17.53", "lodash": "^4.17.15", "prop-types": "^15.7.2" } }, "@superset-ui/core": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/core/-/core-0.17.50.tgz", - "integrity": "sha512-YWCWZOHqsvXjzIGG+gKLJESsoSaobGcvIUQyQ+RN9nmqFJezBIlHenbsDVnn7eHN1jMOBUYTwmv5p9AojLslRw==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/core/-/core-0.17.53.tgz", + "integrity": "sha512-2bIRrK3Y+4ZSNu6drc1EzHTq6fO3aWfdjCh43ytju88nlADHheQXgwxEKnmjzI141qxiVL2+oSL2kC6pSTkW8A==", "requires": { "@babel/runtime": "^7.1.2", "@emotion/cache": "^11.1.3", @@ -66394,12 +66409,12 @@ } }, "@superset-ui/legacy-plugin-chart-calendar": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-calendar/-/legacy-plugin-chart-calendar-0.17.50.tgz", - "integrity": "sha512-jLIYTStx04Jd2jZv7u8FZ9u+3Zf0bd/c1GjToG2w4VnbDc73eTEqiMhyJPGlaZuKABWP2pigEanmbpR0OTAD/g==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-calendar/-/legacy-plugin-chart-calendar-0.17.53.tgz", + "integrity": "sha512-NLevYzzhQyRgP+vdEfhJyDxJIBbGM/bJTJfFw1iRllny3WQax6iU/X5hUw/iWZqruVNkwSnUA39+EGcjU1aIjg==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3-array": "^2.0.3", "d3-selection": "^1.4.0", "d3-tip": "^0.9.1", @@ -66417,24 +66432,24 @@ } }, "@superset-ui/legacy-plugin-chart-chord": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-chord/-/legacy-plugin-chart-chord-0.17.50.tgz", - "integrity": "sha512-XUK6LnUBuhYAHsyqGId80a7f+vzXgVuiZfbFTRJy4M/uPNdIBKfxX1t4kKudHIlqKNoSV2pFIVwh+4h4KpmadQ==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-chord/-/legacy-plugin-chart-chord-0.17.53.tgz", + "integrity": "sha512-a3Y8b/1nSuFvzEzUDTVVmad5/YjTBhz0qU2rcVGrdKp2kzuSVXVVljdN7KVisDUNHhYqrttLM8RQrqGw9f7x1A==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "prop-types": "^15.6.2", "react": "^16.13.1" } }, "@superset-ui/legacy-plugin-chart-country-map": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-country-map/-/legacy-plugin-chart-country-map-0.17.50.tgz", - "integrity": "sha512-toxr09cCUI4Wari215323T8PL5YddCtnvliKVRA4+8UEEU9bnh+gQDIh++UqXq51dAR63czyr4kmxbu//JLDEg==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-country-map/-/legacy-plugin-chart-country-map-0.17.53.tgz", + "integrity": "sha512-zTImQdeBT8raXnxafBIHvaVqOqKoECfyDwgFlPKhs4M7EXPG7U8/VLg0Oi2dCA7/SFZA/ASrJwc/KxW399vJhw==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "d3-array": "^2.0.3", "prop-types": "^15.6.2" @@ -66451,34 +66466,34 @@ } }, "@superset-ui/legacy-plugin-chart-event-flow": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-event-flow/-/legacy-plugin-chart-event-flow-0.17.50.tgz", - "integrity": "sha512-6Zq8CmIMCnqgcJJ8XSqWIexTZBbUG6lZto9isVnxBXLKiGAau6vMOLlWPZjyWPJETProVMnAc+CQm+YRhLI1TQ==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-event-flow/-/legacy-plugin-chart-event-flow-0.17.53.tgz", + "integrity": "sha512-QYL0Feyfu7ZH1GeQ9sfEaEgnW2IQG93sJnM29NO53CjSvdbbZItfU9v6xVnAo6jMwcam7JLNYRtuIPgJevNThw==", "requires": { "@data-ui/event-flow": "^0.0.84", - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-force-directed": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-force-directed/-/legacy-plugin-chart-force-directed-0.17.50.tgz", - "integrity": "sha512-4KvRGdA2974MekvZ87ei/H5rP6MQooHB4PndLriRqqwtfwNs7LDlN3o/SRagKFj/8xvTxrZfprF0Kt+TO6Dk6A==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-force-directed/-/legacy-plugin-chart-force-directed-0.17.53.tgz", + "integrity": "sha512-F7hkrBxC7EWrClQ1jb7anzj1SmIjqXVMz2JKhzwEUk++Tafnn0mrB7Yo51u3twFFOY5bwn+KcI1NObzBRkXguQ==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "prop-types": "^15.7.2" } }, "@superset-ui/legacy-plugin-chart-heatmap": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-heatmap/-/legacy-plugin-chart-heatmap-0.17.50.tgz", - "integrity": "sha512-B/qt/z2ISVkiBRPoGDo4TVsur7QgFG3OKtIzjx6k+8KoRC1oWqeA2zTJAi55lp0bch5Mo4iIUwoyEY45T9nzOA==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-heatmap/-/legacy-plugin-chart-heatmap-0.17.53.tgz", + "integrity": "sha512-NXx/E3AiTxkL+qwaj8B0IDrhWo6P5u5EuXXx1xaWqMTH18YomyeA9l4NBPwsjCfhAMqrEeT0hzeY2/WSoPq5KQ==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "d3-svg-legend": "^1.x", "d3-tip": "^0.9.1", @@ -66486,14 +66501,14 @@ } }, "@superset-ui/legacy-plugin-chart-histogram": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-histogram/-/legacy-plugin-chart-histogram-0.17.50.tgz", - "integrity": "sha512-AO2VbdJERQfSAVTsCVOkCjkzaHOzFhTgyUQpuCNwaMt6sV9yzKR/G3QHYxrfzTT/2DxDEjAX+uV26b821VFA8A==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-histogram/-/legacy-plugin-chart-histogram-0.17.53.tgz", + "integrity": "sha512-EQ/VvG+qCec+IqnwYHA90iHAjkhnPNGkKbTuKlsRyL3ONfxg3n6L4EQOlAA0HvELKkFAZXBxh8TA8Qc3j+g4Fw==", "requires": { "@data-ui/histogram": "^0.0.84", "@data-ui/theme": "^0.0.84", - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "@vx/legend": "^0.0.198", "@vx/responsive": "^0.0.199", "@vx/scale": "^0.0.197", @@ -66561,12 +66576,12 @@ } }, "@superset-ui/legacy-plugin-chart-horizon": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-horizon/-/legacy-plugin-chart-horizon-0.17.50.tgz", - "integrity": "sha512-rfWJtCIITXqkYMW5Ue0MniqKYSQILtdVq8KDAtX2h5KuttDSi/2/ahBdwUmAs4rcjeSHH2FCaJONMAfAV0u1eA==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-horizon/-/legacy-plugin-chart-horizon-0.17.53.tgz", + "integrity": "sha512-LsM4HOuOkiabRNxMUjjietbFx99admne59Mm5zQdsRPNEpN/EKEWu8R4G4crSSqxxzD9KVnveRPE7OD0n91k/A==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3-array": "^2.0.3", "d3-scale": "^3.0.1", "prop-types": "^15.6.2" @@ -66595,12 +66610,12 @@ } }, "@superset-ui/legacy-plugin-chart-map-box": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-map-box/-/legacy-plugin-chart-map-box-0.17.50.tgz", - "integrity": "sha512-9ksPlfBRQHqWuoktnpnRtR0N7l8FbZ0caBvK9I5+zuWDv8/rc5sunjkmDmAAJg3GSWfn7NzNS0spSk6YsfmKOQ==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-map-box/-/legacy-plugin-chart-map-box-0.17.53.tgz", + "integrity": "sha512-JuM77arnxECuSiHkdLMry4JruuVTAfTKTtR8F4qGOpiYiXzGEv4K+y12eqBe1o94ckJF43Esz9e1fdPLDkjqTw==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "immutable": "^3.8.2", "mapbox-gl": "^0.53.0", "prop-types": "^15.6.2", @@ -66617,118 +66632,118 @@ } }, "@superset-ui/legacy-plugin-chart-paired-t-test": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-paired-t-test/-/legacy-plugin-chart-paired-t-test-0.17.50.tgz", - "integrity": "sha512-20jRj92fsGm+fRzeQ8WHP4iFdczO9wMg9jok9OWtZHGUW0Sa3+YpbCW48of9qnqqf1Um1rqZu6PdmgKwevz1zg==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-paired-t-test/-/legacy-plugin-chart-paired-t-test-0.17.53.tgz", + "integrity": "sha512-QkRVm0XGoOxqOX0nRvHnGon2gG8MmV+dbBBpmPkmspxCWKrn183Wzq5SiMlM4vgo2HaroWUIPuBgLBd7rYZtGw==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "distributions": "^1.0.0", "prop-types": "^15.6.2", "reactable": "^1.1.0" } }, "@superset-ui/legacy-plugin-chart-parallel-coordinates": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-parallel-coordinates/-/legacy-plugin-chart-parallel-coordinates-0.17.50.tgz", - "integrity": "sha512-fo8ASuix28TqTPNlVTBWXPXBo99sVpxXaCkXpb4cSnO6F6V6B9Kv9vSKIfV6KZL6ul1wQTe/xLjt/lyyX5HjHg==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-parallel-coordinates/-/legacy-plugin-chart-parallel-coordinates-0.17.53.tgz", + "integrity": "sha512-NcwuEd+rXfmwPshPby0jEgnJnbYfKruM7l0Hb3lIw6iMTc1IV21d1CMftQPvYYdwagam0FapBO2YcSvnvj2rDw==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "prop-types": "^15.7.2" } }, "@superset-ui/legacy-plugin-chart-partition": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-partition/-/legacy-plugin-chart-partition-0.17.50.tgz", - "integrity": "sha512-cHwGtxftPk+j1BowOL12fTuM+eAWbAbfxGF4t+W0X9/ZX5KFpHMYR0cd836nPgfhTtO9sSc4/W34dP0QIQA9hQ==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-partition/-/legacy-plugin-chart-partition-0.17.53.tgz", + "integrity": "sha512-CTzKjaKCdT/+bFlXUDD4nXC2CO7mXmIPJ2K/M94rY2G2gdAWRZJ1i2HlcvTP+RY/AItzZm3C+E7hYdAQ6toBkA==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "d3-hierarchy": "^1.1.8", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-pivot-table": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.17.50.tgz", - "integrity": "sha512-KolczfBy36AnXO445UF8uUDTb9enw9qy7wURr7RcMeBzLG0xN0P8vxEG/OU4NQ3MbbBBi7lYbTuTEYdZ/5wQgw==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.17.53.tgz", + "integrity": "sha512-bk7mttnZFGgGmWCfj0kO++65XsMNyQJch0dgfRRnLVTlSnY89/kGqszTKybbCZhsbx4T5bJ+bn6hZKAGH+FnUA==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "datatables.net-bs": "^1.10.15", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-rose": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.17.50.tgz", - "integrity": "sha512-DW8FKdR/dXlVsavxARnQZTB+hZ7B/C8Kwh+Kwa8Ji+qCJ5p30cH0xGQ9oX5Lgc15L01MIew4sLYaFdE6i41DCA==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.17.53.tgz", + "integrity": "sha512-ppvQuKAS0rMhniKenLXSKczmAsHX4igYc0bVZAvfFDmLNW3tnlmivL+zYSw/sQ9PAhjMGDbTBlSio1oJ+91wiA==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "nvd3": "1.8.6", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-sankey": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey/-/legacy-plugin-chart-sankey-0.17.50.tgz", - "integrity": "sha512-d4kPCsY7nLZWhJDU1oXOC9+jwFoBWGvCI5n+GldJyhiDwxrp9+SBTZCx3ubmhgeI1HETeAD7C99DQ/neT5ttAA==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey/-/legacy-plugin-chart-sankey-0.17.53.tgz", + "integrity": "sha512-3tvMghg5WUAq40su8cZrjJHoc/TsK1WWx6UFu+j2mPOh/BJJZb8wh7A63X82ubLdyzEqdjxsEs9pzZWzs7kUHw==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "d3-sankey": "^0.4.2", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-sankey-loop": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey-loop/-/legacy-plugin-chart-sankey-loop-0.17.50.tgz", - "integrity": "sha512-AMgrl2Te24H5VoveHOjeHyhaI6tQtKo3EGuMb+RAHcgaBv1YRSaMx5LsR90qJtXlxMK1t4kRwG7mhqlE4kEP4Q==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey-loop/-/legacy-plugin-chart-sankey-loop-0.17.53.tgz", + "integrity": "sha512-60aGflqOi5+XDE3BR/p+Pw0xVp7OHsjwroX77CwkwBtFkw1AFVWczaTJH6CYeeCJZXCLYjrbc5OFMuaxIJ+j+Q==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3-sankey-diagram": "^0.7.3", "d3-selection": "^1.4.0", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-sunburst": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sunburst/-/legacy-plugin-chart-sunburst-0.17.50.tgz", - "integrity": "sha512-ZFN4s74tVtxMbHV6rGylup1tWbI3nwOMqFccuP9m/Nk78B2IMuU2ljZxE/2Zd87gbpg5NOH1442yKcbqyYt0kw==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sunburst/-/legacy-plugin-chart-sunburst-0.17.53.tgz", + "integrity": "sha512-t0z7XPsDtDpnZ+fIpn57w9Vi3oWQ7ximDdjmag1WGhC6+dwR3XxEpNcicI6P6xfNX078RT8Iz89PZQBtagAAkA==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-treemap": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.17.50.tgz", - "integrity": "sha512-1W9kH/2rcpo6bZNM4A00mHdSd8BCFuatf7Z45yHGwUnl1monnNcam0d5YxLMgR4NZMYCW76S5ltRPY+2+/weGg==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.17.53.tgz", + "integrity": "sha512-LV16Qwiz7ahfhCmuWIGk6f54KpdRJDAyLtr/ifFi8a2AcoG27Lf7hZZ3mCI9Jl5X6c7LLBmvAHfxdbBnLGa8+g==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3-hierarchy": "^1.1.8", "d3-selection": "^1.4.0", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-world-map": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.17.50.tgz", - "integrity": "sha512-HIomXGPRxI3sejPhW/ktMI673Qd47VrcwkNjsjJSlz8MrlwAMGWNynEwEYAaTmKbm16hoesNN2JuZSVGBAHX4w==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.17.53.tgz", + "integrity": "sha512-gnDBTyWPctqucyQzAObH6N+3f9GUQq9qpQ4cNbtvpIoVgXowYA5Q5dIfXBPnq525t78o3eiWqclTYf2Xcd62Kw==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "d3-array": "^2.4.0", "d3-color": "^1.4.1", @@ -66752,13 +66767,13 @@ } }, "@superset-ui/legacy-preset-chart-big-number": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.50.tgz", - "integrity": "sha512-5ibPRV7LMzhUvFQ1WQgeBrcCaj8vmEk2Ocnuk+Kxk5cFFeOjF6Y/x2XbKz1PBwK1KTB4qC9OT3O6vJcTWHJjLQ==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.53.tgz", + "integrity": "sha512-HUlE6IZUjFvPMiXCj1cdRiR4avFLVhT5qwIQZk1l30kycl8/73rTm37Y/syBfZMPrfCrIW3nyReqfcnAaNqw9g==", "requires": { "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "@types/d3-color": "^1.2.2", "@types/shortid": "^0.0.29", "d3-color": "^1.2.3", @@ -66766,9 +66781,9 @@ } }, "@superset-ui/legacy-preset-chart-deckgl": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-deckgl/-/legacy-preset-chart-deckgl-0.4.6.tgz", - "integrity": "sha512-xXGNj7WQHMA+QpeiHMrinwWhOwskD9ucXoe10AfFFgar9TwvCE6wpgRwnoyF0hjoaXnMqpYyFbzlucCf3WSfVQ==", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-deckgl/-/legacy-preset-chart-deckgl-0.4.7.tgz", + "integrity": "sha512-TaAX1PlZ5DhsNelgoOjCfPBlFtHZDFcozJEIAV2qXzXUo6rfIgskqIq4X3VbMuYnngZw5of4hAtOH1+Tgv+Wmw==", "requires": { "@math.gl/web-mercator": "^3.2.2", "@types/d3-array": "^2.0.0", @@ -66791,13 +66806,13 @@ } }, "@superset-ui/legacy-preset-chart-nvd3": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.17.50.tgz", - "integrity": "sha512-jy2c37BXMQnSUc+pE9ZhiNiPNQHxweO+RD+gQcyF8DJ6Dn+woPE2DCfqjAdfVpUKuI4Y/FOM2lFlZT4xwGzsiA==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.17.53.tgz", + "integrity": "sha512-wTbQRCZDrnb16tLJzXYbIiCFbHddRJ3fo5DKsbv6MFNrfOLWWx1SjAZ5C60e57u33XpKdTE5jGpEuGdq7BZ55w==", "requires": { "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "d3": "^3.5.17", "d3-tip": "^0.9.1", "dompurify": "^2.0.6", @@ -66811,12 +66826,12 @@ } }, "@superset-ui/plugin-chart-echarts": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-echarts/-/plugin-chart-echarts-0.17.50.tgz", - "integrity": "sha512-Og5QRwmYZ66zyUjBsBPBU9JzL1zTtgsyS/opGhMDCBGBJbWmAPi8j6kSO2sVSzrexoTYWtPbOO261uMKzuGZ0w==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-echarts/-/plugin-chart-echarts-0.17.53.tgz", + "integrity": "sha512-XXKqhr2CwZfi02qW55d9SQnNmdewTsAJT6xePBjci0SXAZRmi/T8vRbq2OCDJ7mQ0de7kjVBydAuOEEU/Y554A==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "@types/mathjs": "^6.0.7", "d3-array": "^1.2.0", "echarts": "^5.1.1", @@ -66825,22 +66840,22 @@ } }, "@superset-ui/plugin-chart-pivot-table": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-pivot-table/-/plugin-chart-pivot-table-0.17.50.tgz", - "integrity": "sha512-WEa5lgJ4LbPb2v0MdHcDbDpnSN3khGmjvo2UV4G3AiJ6A47DLje8fhiYHkFhW7HfarozROYrwtcI/Rab6DYEyw==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-pivot-table/-/plugin-chart-pivot-table-0.17.53.tgz", + "integrity": "sha512-18CTaM1sRgK5laFwHlKV+1A7+l9YWwPAvb7XrMjS8CQq0T2aEqNSQm7KWByG+LEj2x86idM8gaWghNes27yVtQ==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", - "@superset-ui/react-pivottable": "^0.12.6" + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", + "@superset-ui/react-pivottable": "^0.12.8" } }, "@superset-ui/plugin-chart-table": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.17.50.tgz", - "integrity": "sha512-L8z9dm/0qPkvo4F8+lLb6yF0x2e7MaRxkZO9RGC0A9HYiMWEUc2Bg9Z+/QmLV6B9mA8qgYQbAeiglfBKC9h7nQ==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.17.53.tgz", + "integrity": "sha512-PSeL/zQSTvQyztjUMMm4U4G6oEM3xk3wkC4HTpuLEpjQ7qyGme39M1JeCGvNG4pPZRm0nO4pU+0U/36oR0lAjw==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "@types/d3-array": "^2.9.0", "@types/react-table": "^7.0.29", "d3-array": "^2.4.0", @@ -66862,12 +66877,12 @@ } }, "@superset-ui/plugin-chart-word-cloud": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-word-cloud/-/plugin-chart-word-cloud-0.17.50.tgz", - "integrity": "sha512-IudEfIEy4JxJhmSrn0nctI4pIJY/KORi9FrT2ZiBRWTh1SmFhZNM2d9vQixsgXq1+bHN9J3JpZ3G0+4sFOtwsw==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-word-cloud/-/plugin-chart-word-cloud-0.17.53.tgz", + "integrity": "sha512-lQTr9UpeoGgX1B0SkjrvtL0zjgYVoJbm6RVv8ELG+efCG1oYAoIVgw2sahJI4zLqNiHcNeWqHUcu7NK06uc4mA==", "requires": { - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "@types/d3-cloud": "^1.2.1", "@types/d3-scale": "^2.0.2", "d3-cloud": "^1.2.5", @@ -66898,14 +66913,14 @@ } }, "@superset-ui/preset-chart-xy": { - "version": "0.17.50", - "resolved": "https://registry.npmjs.org/@superset-ui/preset-chart-xy/-/preset-chart-xy-0.17.50.tgz", - "integrity": "sha512-pUhfI1aojKKDcrFB0aInkL7WmLlKdcOBhddsJ4+c5UfxpQZGdQ9VR9i0PPyvs8dbF4bRWyJNs31h9RHt7NOVSw==", + "version": "0.17.53", + "resolved": "https://registry.npmjs.org/@superset-ui/preset-chart-xy/-/preset-chart-xy-0.17.53.tgz", + "integrity": "sha512-nmqif4Zd7Tdx4hLoDiiRiNFUFn1kliumjp9RQK68eMaefWcl1vTMT7nPmyFvgUH5390HJygpC3up50+j5Bngkg==", "requires": { "@data-ui/theme": "^0.0.84", "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.50", - "@superset-ui/core": "0.17.50", + "@superset-ui/chart-controls": "0.17.53", + "@superset-ui/core": "0.17.53", "@vx/axis": "^0.0.198", "@vx/legend": "^0.0.198", "@vx/scale": "^0.0.197", @@ -67024,9 +67039,9 @@ } }, "@superset-ui/react-pivottable": { - "version": "0.12.6", - "resolved": "https://registry.npmjs.org/@superset-ui/react-pivottable/-/react-pivottable-0.12.6.tgz", - "integrity": "sha512-2+81WL4ocv4VFzgkj3wOBcEgejnJfsJ2D08kMvFfeBt6fhqC35nkendeZMAjl4bFBmzSJIFS6H+agjoeOUyq5A==", + "version": "0.12.8", + "resolved": "https://registry.npmjs.org/@superset-ui/react-pivottable/-/react-pivottable-0.12.8.tgz", + "integrity": "sha512-7DRxX/w1uSQE1pibSe64t1o+fmiP7ZWT2FJkjK510bSJm8NUIPCXtmpK+NKtNZuCteE9sqE7bQxd54SSq2xWKw==", "requires": { "immutability-helper": "^3.1.1", "prop-types": "^15.7.2", @@ -92323,6 +92338,12 @@ "resize-observer-polyfill": "^1.5.1" } }, + "react-reverse-portal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/react-reverse-portal/-/react-reverse-portal-2.0.1.tgz", + "integrity": "sha512-sj/D9nSHspqV8i8hWkTSZ5Ohnrqk2A5fkDKw4Xe/zV4OfF1UYwmbzrxLdmNRdKkWgQwnXIxaa2E3FC7QYdZAeA==", + "requires": {} + }, "react-router": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index deae9ee89d24a..120644c2b81f0 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -67,35 +67,35 @@ "@emotion/babel-preset-css-prop": "^11.2.0", "@emotion/cache": "^11.1.3", "@emotion/react": "^11.1.5", - "@superset-ui/chart-controls": "^0.17.50", - "@superset-ui/core": "^0.17.50", - "@superset-ui/legacy-plugin-chart-calendar": "^0.17.50", - "@superset-ui/legacy-plugin-chart-chord": "^0.17.50", - "@superset-ui/legacy-plugin-chart-country-map": "^0.17.50", - "@superset-ui/legacy-plugin-chart-event-flow": "^0.17.50", - "@superset-ui/legacy-plugin-chart-force-directed": "^0.17.50", - "@superset-ui/legacy-plugin-chart-heatmap": "^0.17.50", - "@superset-ui/legacy-plugin-chart-histogram": "^0.17.50", - "@superset-ui/legacy-plugin-chart-horizon": "^0.17.50", - "@superset-ui/legacy-plugin-chart-map-box": "^0.17.50", - "@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.50", - "@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.50", - "@superset-ui/legacy-plugin-chart-partition": "^0.17.50", - "@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.50", - "@superset-ui/legacy-plugin-chart-rose": "^0.17.50", - "@superset-ui/legacy-plugin-chart-sankey": "^0.17.50", - "@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.50", - "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.50", - "@superset-ui/legacy-plugin-chart-treemap": "^0.17.50", - "@superset-ui/legacy-plugin-chart-world-map": "^0.17.50", - "@superset-ui/legacy-preset-chart-big-number": "^0.17.50", - "@superset-ui/legacy-preset-chart-deckgl": "^0.4.6", - "@superset-ui/legacy-preset-chart-nvd3": "^0.17.50", - "@superset-ui/plugin-chart-echarts": "^0.17.50", - "@superset-ui/plugin-chart-pivot-table": "^0.17.50", - "@superset-ui/plugin-chart-table": "^0.17.50", - "@superset-ui/plugin-chart-word-cloud": "^0.17.50", - "@superset-ui/preset-chart-xy": "^0.17.50", + "@superset-ui/chart-controls": "^0.17.53", + "@superset-ui/core": "^0.17.53", + "@superset-ui/legacy-plugin-chart-calendar": "^0.17.53", + "@superset-ui/legacy-plugin-chart-chord": "^0.17.53", + "@superset-ui/legacy-plugin-chart-country-map": "^0.17.53", + "@superset-ui/legacy-plugin-chart-event-flow": "^0.17.53", + "@superset-ui/legacy-plugin-chart-force-directed": "^0.17.53", + "@superset-ui/legacy-plugin-chart-heatmap": "^0.17.53", + "@superset-ui/legacy-plugin-chart-histogram": "^0.17.53", + "@superset-ui/legacy-plugin-chart-horizon": "^0.17.53", + "@superset-ui/legacy-plugin-chart-map-box": "^0.17.53", + "@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.53", + "@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.53", + "@superset-ui/legacy-plugin-chart-partition": "^0.17.53", + "@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.53", + "@superset-ui/legacy-plugin-chart-rose": "^0.17.53", + "@superset-ui/legacy-plugin-chart-sankey": "^0.17.53", + "@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.53", + "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.53", + "@superset-ui/legacy-plugin-chart-treemap": "^0.17.53", + "@superset-ui/legacy-plugin-chart-world-map": "^0.17.53", + "@superset-ui/legacy-preset-chart-big-number": "^0.17.53", + "@superset-ui/legacy-preset-chart-deckgl": "^0.4.7", + "@superset-ui/legacy-preset-chart-nvd3": "^0.17.53", + "@superset-ui/plugin-chart-echarts": "^0.17.53", + "@superset-ui/plugin-chart-pivot-table": "^0.17.53", + "@superset-ui/plugin-chart-table": "^0.17.53", + "@superset-ui/plugin-chart-word-cloud": "^0.17.53", + "@superset-ui/preset-chart-xy": "^0.17.53", "@vx/responsive": "^0.0.195", "abortcontroller-polyfill": "^1.1.9", "antd": "^4.9.4", @@ -154,6 +154,7 @@ "react-markdown": "^4.3.1", "react-redux": "^7.2.0", "react-resize-detector": "^6.0.1-rc.1", + "react-reverse-portal": "^2.0.1", "react-router-dom": "^5.1.2", "react-search-input": "^0.11.3", "react-select": "^3.1.0", diff --git a/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx index 66eaf20fdbb43..20f63a6fcabd9 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx @@ -50,16 +50,21 @@ jest.mock('src/dashboard/actions/dashboardState'); describe('DashboardBuilder', () => { let favStarStub; + let focusedTabStub; beforeAll(() => { // this is invoked on mount, so we stub it instead of making a request favStarStub = sinon .stub(dashboardStateActions, 'fetchFaveStar') .returns({ type: 'mock-action' }); + focusedTabStub = sinon + .stub(dashboardStateActions, 'setLastFocusedTab') + .returns({ type: 'mock-action' }); }); afterAll(() => { favStarStub.restore(); + focusedTabStub.restore(); }); function setup(overrideState = {}, overrideStore) { diff --git a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx index efc1d8ca91aa5..2a1d0bc98b2bb 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx @@ -36,6 +36,7 @@ import { sliceId } from 'spec/fixtures/mockChartQueries'; import dashboardInfo from 'spec/fixtures/mockDashboardInfo'; import { dashboardLayout as mockLayout } from 'spec/fixtures/mockDashboardLayout'; import { sliceEntitiesForChart } from 'spec/fixtures/mockSliceEntities'; +import { nativeFiltersInfo } from '../../fixtures/mockNativeFilters'; describe('ChartHolder', () => { const props = { @@ -55,6 +56,7 @@ describe('ChartHolder', () => { handleComponentDrop() {}, updateComponents() {}, deleteComponent() {}, + nativeFilters: nativeFiltersInfo.filters, }; function setup(overrideProps) { diff --git a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx index 4b2dbd9a5316e..2f15359c7f192 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx @@ -33,8 +33,10 @@ import HoverMenu from 'src/dashboard/components/menu/HoverMenu'; import DragDroppable from 'src/dashboard/components/dnd/DragDroppable'; import Tabs from 'src/dashboard/components/gridComponents/Tabs'; import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants'; +import emptyDashboardLayout from 'src/dashboard/fixtures/emptyDashboardLayout'; import { dashboardLayoutWithTabs } from 'spec/fixtures/mockDashboardLayout'; import { mockStoreWithTabs } from 'spec/fixtures/mockStore'; +import { nativeFilters } from 'spec/fixtures/mockNativeFilters'; describe('Tabs', () => { fetchMock.post('glob:*/r/shortner/', {}); @@ -59,6 +61,8 @@ describe('Tabs', () => { deleteComponent() {}, updateComponents() {}, logEvent() {}, + dashboardLayout: emptyDashboardLayout, + nativeFilters: nativeFilters.filters, }; function setup(overrideProps) { diff --git a/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx b/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx index 4b672b4a842a6..5c355109676d1 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx +++ b/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx @@ -110,7 +110,7 @@ describe('FiltersConfigModal', () => { function addFilter() { act(() => { - wrapper.find('button[aria-label="Add tab"]').at(0).simulate('click'); + wrapper.find('[aria-label="Add filter"]').at(0).simulate('click'); }); } diff --git a/superset-frontend/spec/javascripts/datasource/DatasourceEditor_spec.jsx b/superset-frontend/spec/javascripts/datasource/DatasourceEditor_spec.jsx index a9e1454b0e4fa..2ab5ea296bd59 100644 --- a/superset-frontend/spec/javascripts/datasource/DatasourceEditor_spec.jsx +++ b/superset-frontend/spec/javascripts/datasource/DatasourceEditor_spec.jsx @@ -86,6 +86,7 @@ describe('DatasourceEditor', () => { nullable: true, default: '', primary_key: false, + is_dttm: true, }, { name: 'gender', @@ -93,6 +94,7 @@ describe('DatasourceEditor', () => { nullable: true, default: '', primary_key: false, + is_dttm: false, }, { name: 'new_column', @@ -100,6 +102,7 @@ describe('DatasourceEditor', () => { nullable: true, default: '', primary_key: false, + is_dttm: false, }, ]; diff --git a/superset-frontend/spec/javascripts/explore/controlUtils_spec.tsx b/superset-frontend/spec/javascripts/explore/controlUtils_spec.tsx index 7c4a69d9aef89..f50495602342f 100644 --- a/superset-frontend/spec/javascripts/explore/controlUtils_spec.tsx +++ b/superset-frontend/spec/javascripts/explore/controlUtils_spec.tsx @@ -149,30 +149,14 @@ describe('controlUtils', () => { expect(control).toBeNull(); }); - it('applies the default function for metrics', () => { + it('metrics control should be empty by default', () => { const control = getControlState('metrics', 'table', state); - expect(control?.default).toEqual(['first']); + expect(control?.default).toBeUndefined(); }); - it('applies the default function for metric', () => { + it('metric control should be empty by default', () => { const control = getControlState('metric', 'table', state); - expect(control?.default).toEqual('first'); - }); - - it('applies the default function, prefers count if it exists', () => { - const stateWithCount = { - ...state, - datasource: { - ...(state.datasource as DatasourceMeta), - metrics: [ - { metric_name: 'first' }, - { metric_name: 'second' }, - { metric_name: 'count' }, - ], - }, - }; - const control = getControlState('metrics', 'table', stateWithCount); - expect(control?.default).toEqual(['count']); + expect(control?.default).toBeUndefined(); }); it('should not apply mapStateToProps when initializing', () => { @@ -180,7 +164,6 @@ describe('controlUtils', () => { ...state, controls: undefined, }); - expect(typeof control?.default).toBe('function'); expect(control?.value).toBe(undefined); }); }); diff --git a/superset-frontend/spec/javascripts/sqllab/TableElement_spec.jsx b/superset-frontend/spec/javascripts/sqllab/TableElement_spec.jsx index d55720637b637..8c07008815aeb 100644 --- a/superset-frontend/spec/javascripts/sqllab/TableElement_spec.jsx +++ b/superset-frontend/spec/javascripts/sqllab/TableElement_spec.jsx @@ -43,7 +43,7 @@ describe('TableElement', () => { it('renders with props', () => { expect(React.isValidElement()).toBe(true); }); - it('has 4 IconTooltip elements', () => { + it('has 5 IconTooltip elements', () => { const wrapper = mount( @@ -55,14 +55,14 @@ describe('TableElement', () => { }, }, ); - expect(wrapper.find(IconTooltip)).toHaveLength(4); + expect(wrapper.find(IconTooltip)).toHaveLength(5); }); it('has 14 columns', () => { const wrapper = shallow(); expect(wrapper.find(ColumnElement)).toHaveLength(14); }); it('mounts', () => { - mount( + const wrapper = mount( , @@ -73,6 +73,8 @@ describe('TableElement', () => { }, }, ); + + expect(wrapper.find(TableElement)).toHaveLength(1); }); it('fades table', async () => { const wrapper = mount( @@ -86,13 +88,11 @@ describe('TableElement', () => { }, }, ); - expect(wrapper.find(TableElement).state().hovered).toBe(false); expect(wrapper.find('[data-test="fade"]').first().props().hovered).toBe( false, ); wrapper.find('.header-container').hostNodes().simulate('mouseEnter'); await waitForComponentToPaint(wrapper, 300); - expect(wrapper.find(TableElement).state().hovered).toBe(true); expect(wrapper.find('[data-test="fade"]').first().props().hovered).toBe( true, ); @@ -111,12 +111,22 @@ describe('TableElement', () => { }, }, ); - expect(wrapper.find(TableElement).state().sortColumns).toBe(false); + expect( + wrapper.find(IconTooltip).at(2).hasClass('fa-sort-alpha-asc'), + ).toEqual(true); + expect( + wrapper.find(IconTooltip).at(2).hasClass('fa-sort-numeric-asc'), + ).toEqual(false); wrapper.find('.header-container').hostNodes().simulate('click'); expect(wrapper.find(ColumnElement).first().props().column.name).toBe('id'); wrapper.find('.header-container').simulate('mouseEnter'); wrapper.find('.sort-cols').hostNodes().simulate('click'); - expect(wrapper.find(TableElement).state().sortColumns).toBe(true); + expect( + wrapper.find(IconTooltip).at(2).hasClass('fa-sort-numeric-asc'), + ).toEqual(true); + expect( + wrapper.find(IconTooltip).at(2).hasClass('fa-sort-alpha-asc'), + ).toEqual(false); expect(wrapper.find(ColumnElement).first().props().column.name).toBe( 'active', ); diff --git a/superset-frontend/src/SqlLab/components/QueryTable.jsx b/superset-frontend/src/SqlLab/components/QueryTable/index.jsx similarity index 66% rename from superset-frontend/src/SqlLab/components/QueryTable.jsx rename to superset-frontend/src/SqlLab/components/QueryTable/index.jsx index e9414e59c2169..b3bbed99cdba0 100644 --- a/superset-frontend/src/SqlLab/components/QueryTable.jsx +++ b/superset-frontend/src/SqlLab/components/QueryTable/index.jsx @@ -22,16 +22,18 @@ import moment from 'moment'; import Card from 'src/components/Card'; import ProgressBar from 'src/components/ProgressBar'; import Label from 'src/components/Label'; -import { t, css } from '@superset-ui/core'; +import { t, styled } from '@superset-ui/core'; import { useSelector } from 'react-redux'; import TableView from 'src/components/TableView'; import Button from 'src/components/Button'; import { fDuration } from 'src/modules/dates'; -import { IconTooltip } from '../../components/IconTooltip'; -import ResultSet from './ResultSet'; -import ModalTrigger from '../../components/ModalTrigger'; -import HighlightedSql from './HighlightedSql'; -import QueryStateLabel from './QueryStateLabel'; +import Icons from 'src/components/Icons'; +import Icon from 'src/components/Icon'; +import { Tooltip } from 'src/components/Tooltip'; +import ResultSet from '../ResultSet'; +import ModalTrigger from '../../../components/ModalTrigger'; +import HighlightedSql from '../HighlightedSql'; +import { StaticPosition, verticalAlign, StyledTooltip } from './styles'; const propTypes = { columns: PropTypes.array, @@ -53,16 +55,78 @@ const openQuery = id => { window.open(url); }; -const StaticPosition = css` - position: static; +const statusAttributes = { + success: { + color: ({ theme }) => theme.colors.success.base, + config: { + name: 'check', + label: t('Success'), + status: 'success', + }, + }, + failed: { + color: ({ theme }) => theme.colors.error.base, + config: { + name: 'x-small', + label: t('Failed'), + status: 'failed', + }, + }, + stopped: { + color: ({ theme }) => theme.colors.error.base, + config: { + name: 'x-small', + label: t('Failed'), + status: 'failed', + }, + }, + running: { + color: ({ theme }) => theme.colors.primary.base, + config: { + name: 'running', + label: t('Running'), + status: 'running', + }, + }, + timed_out: { + color: ({ theme }) => theme.colors.grayscale.light1, + config: { + name: 'offline', + label: t('Offline'), + status: 'offline', + }, + }, + scheduled: { + name: 'queued', + label: t('Scheduled'), + status: 'queued', + }, + pending: { + name: 'queued', + label: t('Scheduled'), + status: 'queued', + }, +}; + +const StatusIcon = styled(Icon, { + shouldForwardProp: prop => prop !== 'status', +})` + color: ${({ status, theme }) => + statusAttributes[status]?.color || theme.colors.grayscale.base}; `; const QueryTable = props => { + const setHeaders = column => { + if (column === 'sql') { + return column.toUpperCase(); + } + return column.charAt(0).toUpperCase().concat(column.slice(1)); + }; const columns = useMemo( () => props.columns.map(column => ({ accessor: column, - Header: column, + Header: () => setHeaders(column), disableSortBy: true, })), [props.columns], @@ -176,44 +240,59 @@ const QueryTable = props => { q.ctas && q.tempTable && q.tempTable.includes('.') ? '' : q.schema; q.output = [schemaUsed, q.tempTable].filter(v => v).join('.'); } - q.progress = ( - - ); - let errorTooltip; - if (q.errorMessage) { - errorTooltip = ( - - - + q.progress = + q.state === 'success' ? ( + + ) : ( + ); - } q.state = ( -
- - {errorTooltip} -
+ + + + + ); q.actions = (
- restoreSql(query)} tooltip={t( 'Overwrite text in the editor with a query on this table', )} placement="top" - /> - + + + openQueryInNewTab(query)} tooltip={t('Run query in a new tab')} placement="top" - /> - + + + removeQuery(query)} - /> + > + +
); return q; diff --git a/superset-frontend/src/SqlLab/components/QueryTable/styles.ts b/superset-frontend/src/SqlLab/components/QueryTable/styles.ts new file mode 100644 index 0000000000000..2800f09945636 --- /dev/null +++ b/superset-frontend/src/SqlLab/components/QueryTable/styles.ts @@ -0,0 +1,41 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { styled, css } from '@superset-ui/core'; +import { IconTooltip } from '../../../components/IconTooltip'; + +export const StaticPosition = css` + position: static; +`; + +export const verticalAlign = css` + vertical-align: 0em; + svg { + height: 0.9em; + } +`; + +export const StyledTooltip = styled(IconTooltip)` + padding-right: ${({ theme }) => theme.gridUnit * 2}px; + span { + color: ${({ theme }) => theme.colors.grayscale.base}; + &: hover { + color: ${({ theme }) => theme.colors.primary.base}; + } + } +`; diff --git a/superset-frontend/src/SqlLab/components/ResultSet.tsx b/superset-frontend/src/SqlLab/components/ResultSet.tsx index a8fe019c7e6a0..b5a6e4faaa0a5 100644 --- a/superset-frontend/src/SqlLab/components/ResultSet.tsx +++ b/superset-frontend/src/SqlLab/components/ResultSet.tsx @@ -271,9 +271,23 @@ export default class ResultSet extends React.PureComponent< return; } - const { schema, sql, dbId, templateParams } = this.props.query; + const { schema, sql, dbId } = this.props.query; + let { templateParams } = this.props.query; const selectedColumns = this.props.query?.results?.selected_columns || []; + // The filters param is only used to test jinja templates. + // Remove the special filters entry from the templateParams + // before saving the dataset. + if (templateParams) { + const p = JSON.parse(templateParams); + /* eslint-disable-next-line no-underscore-dangle */ + if (p._filters) { + /* eslint-disable-next-line no-underscore-dangle */ + delete p._filters; + templateParams = JSON.stringify(p); + } + } + this.props.actions .createDatasource({ schema, @@ -527,7 +541,7 @@ export default class ResultSet extends React.PureComponent< let limitMessage; const limitReached = results?.displayLimitReached; const limit = queryLimit || results.query.limit; - const isAdmin = !!this.props.user?.roles.Admin; + const isAdmin = !!this.props.user?.roles?.Admin; const displayMaxRowsReachedMessage = { withAdmin: t( `The number of results displayed is limited to %(rows)d by the configuration DISPLAY_MAX_ROWS. `, diff --git a/superset-frontend/src/SqlLab/components/TableElement.jsx b/superset-frontend/src/SqlLab/components/TableElement.jsx index e44cccf0d695e..384771a96e83e 100644 --- a/superset-frontend/src/SqlLab/components/TableElement.jsx +++ b/superset-frontend/src/SqlLab/components/TableElement.jsx @@ -16,16 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import Collapse from 'src/components/Collapse'; import Card from 'src/components/Card'; import ButtonGroup from 'src/components/ButtonGroup'; -import shortid from 'shortid'; import { t, styled } from '@superset-ui/core'; import { debounce } from 'lodash'; import { Tooltip } from 'src/components/Tooltip'; +import Icons from 'src/components/Icons'; import CopyToClipboard from '../../components/CopyToClipboard'; import { IconTooltip } from '../../components/IconTooltip'; import ColumnElement from './ColumnElement'; @@ -56,44 +56,26 @@ const Fade = styled.div` opacity: ${props => (props.hovered ? 1 : 0)}; `; -class TableElement extends React.PureComponent { - constructor(props) { - super(props); - this.state = { - sortColumns: false, - hovered: false, - }; - this.toggleSortColumns = this.toggleSortColumns.bind(this); - this.removeTable = this.removeTable.bind(this); - this.setHover = debounce(this.setHover.bind(this), 100); - } +const TableElement = props => { + const [sortColumns, setSortColumns] = useState(false); + const [hovered, setHovered] = useState(false); - setHover(hovered) { - this.setState({ hovered }); - } + const { table, actions, isActive } = props; - popSelectStar() { - const qe = { - id: shortid.generate(), - title: this.props.table.name, - dbId: this.props.table.dbId, - autorun: true, - sql: this.props.table.selectStar, - }; - this.props.actions.addQueryEditor(qe); - } + const setHover = hovered => { + debounce(() => setHovered(hovered), 100)(); + }; - removeTable() { - this.props.actions.removeDataPreview(this.props.table); - this.props.actions.removeTable(this.props.table); - } + const removeTable = () => { + actions.removeDataPreview(table); + actions.removeTable(table); + }; - toggleSortColumns() { - this.setState(prevState => ({ sortColumns: !prevState.sortColumns })); - } + const toggleSortColumns = () => { + setSortColumns(prevState => !prevState); + }; - renderWell() { - const { table } = this.props; + const renderWell = () => { let header; if (table.partitions) { let partitionQuery; @@ -126,12 +108,11 @@ class TableElement extends React.PureComponent { ); } return header; - } + }; - renderControls() { + const renderControls = () => { let keyLink; - const { table } = this.props; - if (table.indexes && table.indexes.length > 0) { + if (table?.indexes?.length) { keyLink = ( + } text={table.selectStar} shouldShowText={false} - tooltipText={t('Copy SELECT statement to the clipboard')} /> )} {table.view && ( @@ -187,56 +170,52 @@ class TableElement extends React.PureComponent { )} ); - } + }; - renderHeader() { - const { table } = this.props; - return ( -
this.setHover(true)} - onMouseLeave={() => this.setHover(false)} + const renderHeader = () => ( +
setHover(true)} + onMouseLeave={() => setHover(false)} + > + - - - {table.name} - - + + {table.name} + + -
- {table.isMetadataLoading || table.isExtraMetadataLoading ? ( - - ) : ( - e.stopPropagation()} - > - {this.renderControls()} - - )} -
+
+ {table.isMetadataLoading || table.isExtraMetadataLoading ? ( + + ) : ( + e.stopPropagation()} + > + {renderControls()} + + )}
- ); - } +
+ ); - renderBody() { - const { table } = this.props; + const renderBody = () => { let cols; if (table.columns) { cols = table.columns.slice(); - if (this.state.sortColumns) { + if (sortColumns) { cols.sort((a, b) => { const colA = a.name.toUpperCase(); const colB = b.name.toUpperCase(); @@ -253,33 +232,54 @@ class TableElement extends React.PureComponent { const metadata = (
this.setHover(true)} - onMouseLeave={() => this.setHover(false)} + onMouseEnter={() => setHover(true)} + onMouseLeave={() => setHover(false)} css={{ paddingTop: 6 }} > - {this.renderWell()} + {renderWell()}
- {cols && - cols.map(col => )} + {cols?.map(col => ( + + ))}
); return metadata; - } + }; - render() { - return ( - - {this.renderBody()} - - ); - } -} + const collapseExpandIcon = () => ( + + + + ); + + return ( + + {renderBody()} + + ); +}; TableElement.propTypes = propTypes; TableElement.defaultProps = defaultProps; diff --git a/superset-frontend/src/SqlLab/main.less b/superset-frontend/src/SqlLab/main.less index 93bf91dc4e5ba..7e5cf6eebb4f1 100644 --- a/superset-frontend/src/SqlLab/main.less +++ b/superset-frontend/src/SqlLab/main.less @@ -414,6 +414,7 @@ div.tablePopover { display: flex; flex: 1; align-items: center; + width: 100%; .table-name { white-space: nowrap; diff --git a/superset-frontend/src/components/Form/FormItem.tsx b/superset-frontend/src/components/Form/FormItem.tsx index ab301a883e543..9b529dbd5d141 100644 --- a/superset-frontend/src/components/Form/FormItem.tsx +++ b/superset-frontend/src/components/Form/FormItem.tsx @@ -35,7 +35,7 @@ const StyledItem = styled(Form.Item)` &::after { display: inline-block; color: ${theme.colors.error.base}; - font-size: ${theme.typography.sizes.m}px; + font-size: ${theme.typography.sizes.s}px; content: '*'; } } diff --git a/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx b/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx index 8569b554a020d..75df2bb088cbb 100644 --- a/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx +++ b/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx @@ -25,17 +25,18 @@ import FormLabel from './FormLabel'; export interface LabeledErrorBoundInputProps { label?: string; validationMethods: - | { onBlur: (value: any) => string } - | { onChange: (value: any) => string }; + | { onBlur: (value: any) => void } + | { onChange: (value: any) => void }; errorMessage: string | null; helpText?: string; required?: boolean; id?: string; + classname?: string; [x: string]: any; } const StyledInput = styled(Input)` - margin: 8px 0; + margin: ${({ theme }) => `${theme.gridUnit}px 0 ${theme.gridUnit * 2}px`}; `; const alertIconStyles = (theme: SupersetTheme, hasError: boolean) => css` @@ -60,6 +61,12 @@ const alertIconStyles = (theme: SupersetTheme, hasError: boolean) => css` } }`} `; +const StyledFormGroup = styled('div')` + margin-bottom: ${({ theme }) => theme.gridUnit * 5}px; + .ant-form-item { + margin-bottom: 0; + } +`; const LabeledErrorBoundInput = ({ label, @@ -68,9 +75,10 @@ const LabeledErrorBoundInput = ({ helpText, required = false, id, + className, ...props }: LabeledErrorBoundInputProps) => ( - <> + {label} @@ -83,7 +91,7 @@ const LabeledErrorBoundInput = ({ > - + ); export default LabeledErrorBoundInput; diff --git a/superset-frontend/src/components/IconButton/IconButton.stories.tsx b/superset-frontend/src/components/IconButton/IconButton.stories.tsx new file mode 100644 index 0000000000000..45435e70cc8ec --- /dev/null +++ b/superset-frontend/src/components/IconButton/IconButton.stories.tsx @@ -0,0 +1,58 @@ +/** + * 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 IconButton, { IconButtonProps } from '.'; + +export default { + title: 'IconButton', + component: IconButton, +}; + +export const InteractiveIconButton = (args: IconButtonProps) => ( + +); + +InteractiveIconButton.args = { + buttonText: 'This is the IconButton text', + altText: 'This is an example of non-default alt text', + href: 'https://preset.io/', + target: '_blank', +}; + +InteractiveIconButton.argTypes = { + icon: { + defaultValue: '/images/icons/sql.svg', + control: { + type: 'select', + options: [ + '/images/icons/sql.svg', + '/images/icons/server.svg', + '/images/icons/image.svg', + 'Click to see example alt text', + ], + }, + }, +}; diff --git a/superset-frontend/src/components/IconButton/IconButton.test.jsx b/superset-frontend/src/components/IconButton/IconButton.test.jsx new file mode 100644 index 0000000000000..40490011953fa --- /dev/null +++ b/superset-frontend/src/components/IconButton/IconButton.test.jsx @@ -0,0 +1,38 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { render, screen } from 'spec/helpers/testing-library'; +import IconButton from 'src/components/IconButton'; + +const defaultProps = { + buttonText: 'This is the IconButton text', + icon: '/images/icons/sql.svg', +}; + +describe('IconButton', () => { + it('renders an IconButton', () => { + render(); + + const icon = screen.getByRole('img'); + const buttonText = screen.getByText(/this is the iconbutton text/i); + + expect(icon).toBeVisible(); + expect(buttonText).toBeVisible(); + }); +}); diff --git a/superset-frontend/src/components/IconButton/index.tsx b/superset-frontend/src/components/IconButton/index.tsx new file mode 100644 index 0000000000000..e7f9c2d89d528 --- /dev/null +++ b/superset-frontend/src/components/IconButton/index.tsx @@ -0,0 +1,123 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { styled } from '@superset-ui/core'; +import Button from 'src/components/Button'; +import { ButtonProps as AntdButtonProps } from 'antd/lib/button'; + +export interface IconButtonProps extends AntdButtonProps { + buttonText: string; + icon: string; + altText?: string; +} + +const StyledButton = styled(Button)` + height: auto; + display: flex; + flex-direction: column; + padding: 0; +`; +const StyledImage = styled.div` + margin: ${({ theme }) => theme.gridUnit * 8}px 0; + padding: ${({ theme }) => theme.gridUnit * 4}px; + + &:first-of-type { + margin-right: 0; + } + + img { + width: fit-content; + + &:first-of-type { + margin-right: 0; + } + } +`; + +const StyledInner = styled.div` + max-height: calc(1.5em * 2); + overflow: hidden; + padding-right: 1rem; + position: relative; + white-space: break-spaces; + + &::before { + content: '...'; + inset-block-end: 0; /* "bottom" */ + inset-inline-end: 8px; /* "right" */ + position: absolute; + } + + &::after { + background-color: ${({ theme }) => theme.colors.grayscale.light4}; + content: ''; + height: 1rem; + inset-inline-end: 8px; /* "right" */ + position: absolute; + top: 4px; + width: 1rem; + } +`; + +const StyledBottom = styled.div` + padding: ${({ theme }) => theme.gridUnit * 6}px + ${({ theme }) => theme.gridUnit * 4}px; + border-radius: 0 0 ${({ theme }) => theme.borderRadius}px + ${({ theme }) => theme.borderRadius}px; + background-color: ${({ theme }) => theme.colors.grayscale.light4}; + width: 100%; + line-height: 1.5em; + overflow: hidden; + white-space: no-wrap; + text-overflow: ellipsis; + + &:first-of-type { + margin-right: 0; + } +`; + +const IconButton = styled( + ({ icon, altText, buttonText, ...props }: IconButtonProps) => ( + + + {altText} + + + {buttonText} + + + ), +)` + text-transform: none; + background-color: ${({ theme }) => theme.colors.grayscale.light5}; + font-weight: ${({ theme }) => theme.typography.weights.normal}; + color: ${({ theme }) => theme.colors.grayscale.dark2}; + border: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; + margin: 0; + width: 100%; + + &:hover, + &:focus { + background-color: ${({ theme }) => theme.colors.grayscale.light5}; + color: ${({ theme }) => theme.colors.grayscale.dark2}; + border: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; + } +`; + +export default IconButton; diff --git a/superset-frontend/src/components/Menu/Menu.tsx b/superset-frontend/src/components/Menu/Menu.tsx index 6733d6a059228..0875b85f8a661 100644 --- a/superset-frontend/src/components/Menu/Menu.tsx +++ b/superset-frontend/src/components/Menu/Menu.tsx @@ -27,6 +27,7 @@ import { Row, Col, Grid } from 'antd'; import Icon from 'src/components/Icon'; import RightMenu from './MenuRight'; import { Languages } from './LanguagePicker'; +import { URL_PARAMS } from '../../constants'; interface BrandProps { path: string; @@ -158,7 +159,7 @@ export function Menu({ return () => window.removeEventListener('resize', windowResize); }, []); - const standalone = getUrlParam('standalone', 'boolean'); + const standalone = getUrlParam(URL_PARAMS.standalone); if (standalone) return <>; const renderSubMenu = ({ diff --git a/superset-frontend/src/components/Menu/MenuRight.tsx b/superset-frontend/src/components/Menu/MenuRight.tsx index 9263c1c621259..a722834bdf442 100644 --- a/superset-frontend/src/components/Menu/MenuRight.tsx +++ b/superset-frontend/src/components/Menu/MenuRight.tsx @@ -27,7 +27,7 @@ import { NavBarProps, MenuObjectProps } from './Menu'; export const dropdownItems = [ { label: t('SQL query'), - url: '/superset/sqllab', + url: '/superset/sqllab?new=true', icon: 'fa-fw fa-search', }, { diff --git a/superset-frontend/src/components/Tabs/Tabs.tsx b/superset-frontend/src/components/Tabs/Tabs.tsx index f5a1d148f36a5..704d84b5220f0 100644 --- a/superset-frontend/src/components/Tabs/Tabs.tsx +++ b/superset-frontend/src/components/Tabs/Tabs.tsx @@ -66,11 +66,6 @@ const StyledTabs = ({ .ant-tabs-nav-list { width: 100%; } - - .ant-tabs-tab { - width: 0; - margin-right: 0; - } `}; .ant-tabs-tab-btn { diff --git a/superset-frontend/src/constants.ts b/superset-frontend/src/constants.ts index ad7683681b939..bfa7033697cbc 100644 --- a/superset-frontend/src/constants.ts +++ b/superset-frontend/src/constants.ts @@ -23,9 +23,19 @@ export const BOOL_TRUE_DISPLAY = 'True'; export const BOOL_FALSE_DISPLAY = 'False'; export const URL_PARAMS = { - standalone: 'standalone', - preselectFilters: 'preselect_filters', -}; + standalone: { + name: 'standalone', + type: 'number', + }, + preselectFilters: { + name: 'preselect_filters', + type: 'object', + }, + showFilters: { + name: 'show_filters', + type: 'boolean', + }, +} as const; /** * Faster debounce delay for inputs without expensive operation. diff --git a/superset-frontend/src/dashboard/actions/dashboardState.js b/superset-frontend/src/dashboard/actions/dashboardState.js index ac2ead0d74330..d0b39e2ff14d7 100644 --- a/superset-frontend/src/dashboard/actions/dashboardState.js +++ b/superset-frontend/src/dashboard/actions/dashboardState.js @@ -344,6 +344,11 @@ export function setDirectPathToChild(path) { return { type: SET_DIRECT_PATH, path }; } +export const SET_LAST_FOCUSED_TAB = 'SET_LAST_FOCUSED_TAB'; +export function setLastFocusedTab(tabId) { + return { type: SET_LAST_FOCUSED_TAB, tabId }; +} + export const SET_FOCUSED_FILTER_FIELD = 'SET_FOCUSED_FILTER_FIELD'; export function setFocusedFilterField(chartId, column) { return { type: SET_FOCUSED_FILTER_FIELD, chartId, column }; diff --git a/superset-frontend/src/dashboard/actions/hydrate.js b/superset-frontend/src/dashboard/actions/hydrate.js index 2928df65737c2..62751033ad672 100644 --- a/superset-frontend/src/dashboard/actions/hydrate.js +++ b/superset-frontend/src/dashboard/actions/hydrate.js @@ -27,7 +27,6 @@ import { import { chart } from 'src/chart/chartReducer'; import { initSliceEntities } from 'src/dashboard/reducers/sliceEntities'; import { getInitialState as getInitialNativeFilterState } from 'src/dashboard/reducers/nativeFilters'; -import { getParam } from 'src/modules/utils'; import { applyDefaultFormData } from 'src/explore/store'; import { buildActiveFilters } from 'src/dashboard/util/activeDashboardFilters'; import findPermission, { @@ -54,6 +53,8 @@ import getFilterConfigsFromFormdata from 'src/dashboard/util/getFilterConfigsFro import getLocationHash from 'src/dashboard/util/getLocationHash'; import newComponentFactory from 'src/dashboard/util/newComponentFactory'; import { TIME_RANGE } from 'src/visualizations/FilterBox/FilterBox'; +import { URL_PARAMS } from 'src/constants'; +import { getUrlParam } from 'src/utils/urlUtils'; import { FeatureFlag, isFeatureEnabled } from '../../featureFlags'; import extractUrlParams from '../util/extractUrlParams'; @@ -77,9 +78,9 @@ export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => ( }); try { // allow request parameter overwrite dashboard metadata - preselectFilters = JSON.parse( - getParam('preselect_filters') || metadata.default_filters, - ); + preselectFilters = + getUrlParam(URL_PARAMS.preselectFilters) || + JSON.parse(metadata.default_filters); } catch (e) { // } @@ -376,6 +377,7 @@ export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => ( hasUnsavedChanges: false, maxUndoHistoryExceeded: false, lastModifiedTime: dashboardData.changed_on, + lastFocusedTabId: null, }, dashboardLayout, }, diff --git a/superset-frontend/src/dashboard/actions/nativeFilters.ts b/superset-frontend/src/dashboard/actions/nativeFilters.ts index 23ce35d241de8..e01a5a67244ef 100644 --- a/superset-frontend/src/dashboard/actions/nativeFilters.ts +++ b/superset-frontend/src/dashboard/actions/nativeFilters.ts @@ -85,11 +85,19 @@ export const setFilterConfiguration = ( endpoint: `/api/v1/dashboard/${id}`, }); + const mergedFilterConfig = filterConfig.map(filter => { + const oldFilter = oldFilters[filter.id]; + if (!oldFilter) { + return filter; + } + return { ...oldFilter, ...filter }; + }); + try { const response = await updateDashboard({ json_metadata: JSON.stringify({ ...metadata, - native_filter_configuration: filterConfig, + native_filter_configuration: mergedFilterConfig, }), }); dispatch( @@ -99,12 +107,20 @@ export const setFilterConfiguration = ( ); dispatch({ type: SET_FILTER_CONFIG_COMPLETE, - filterConfig, + filterConfig: mergedFilterConfig, }); - dispatch(setDataMaskForFilterConfigComplete(filterConfig, oldFilters)); + dispatch( + setDataMaskForFilterConfigComplete(mergedFilterConfig, oldFilters), + ); } catch (err) { - dispatch({ type: SET_FILTER_CONFIG_FAIL, filterConfig }); - dispatch({ type: SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL, filterConfig }); + dispatch({ + type: SET_FILTER_CONFIG_FAIL, + filterConfig: mergedFilterConfig, + }); + dispatch({ + type: SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL, + filterConfig: mergedFilterConfig, + }); } }; diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx index 877cea9c3fa23..807fe46b75dea 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx @@ -120,7 +120,9 @@ const DashboardBuilder: FC = () => { isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS) && (canEdit || (!canEdit && filterValues.length !== 0)); - const [dashboardFiltersOpen, setDashboardFiltersOpen] = useState(true); + const [dashboardFiltersOpen, setDashboardFiltersOpen] = useState( + getUrlParam(URL_PARAMS.showFilters) ?? true, + ); const toggleDashboardFiltersOpen = (visible?: boolean) => { setDashboardFiltersOpen(visible ?? !dashboardFiltersOpen); @@ -152,7 +154,7 @@ const DashboardBuilder: FC = () => { : undefined; const hideDashboardHeader = - getUrlParam(URL_PARAMS.standalone, 'number') === + getUrlParam(URL_PARAMS.standalone) === DashboardStandaloneMode.HIDE_NAV_AND_TITLE; const barTopOffset = diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx index 9c58d853e4c1f..00c5d73ae5b71 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx @@ -21,7 +21,7 @@ import { ParentSize } from '@vx/responsive'; import Tabs from 'src/components/Tabs'; import React, { FC, useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import DashboardGrid from 'src/dashboard/containers/DashboardGrid'; import getLeafComponentIdFromPath from 'src/dashboard/util/getLeafComponentIdFromPath'; import { DashboardLayout, LayoutItem, RootState } from 'src/dashboard/types'; @@ -30,6 +30,10 @@ import { DASHBOARD_ROOT_DEPTH, } from 'src/dashboard/util/constants'; import { getRootLevelTabIndex } from './utils'; +import { Filters } from '../../reducers/types'; +import { getChartIdsInFilterScope } from '../../util/activeDashboardFilters'; +import { findTabsWithChartsInScope } from '../nativeFilters/utils'; +import { setFilterConfiguration } from '../../actions/nativeFilters'; type DashboardContainerProps = { topLevelTabs?: LayoutItem; @@ -39,6 +43,9 @@ const DashboardContainer: FC = ({ topLevelTabs }) => { const dashboardLayout = useSelector( state => state.dashboardLayout.present, ); + const nativeFilters = useSelector( + state => state.nativeFilters.filters, + ); const directPathToChild = useSelector( state => state.dashboardState.directPathToChild, ); @@ -46,10 +53,37 @@ const DashboardContainer: FC = ({ topLevelTabs }) => { getRootLevelTabIndex(dashboardLayout, directPathToChild), ); + const dispatch = useDispatch(); + useEffect(() => { setTabIndex(getRootLevelTabIndex(dashboardLayout, directPathToChild)); }, [getLeafComponentIdFromPath(directPathToChild)]); + // recalculate charts and tabs in scopes of native filters only when a scope or dashboard layout changes + const nativeFiltersValues = Object.values(nativeFilters); + const scopes = nativeFiltersValues.map(filter => filter.scope); + useEffect(() => { + nativeFiltersValues.forEach(filter => { + const filterScope = filter.scope; + const chartsInScope = getChartIdsInFilterScope({ + filterScope: { + scope: filterScope.rootPath, + // @ts-ignore + immune: filterScope.excluded, + }, + }); + const tabsInScope = findTabsWithChartsInScope( + dashboardLayout, + chartsInScope, + ); + Object.assign(filter, { + chartsInScope, + tabsInScope: Array.from(tabsInScope), + }); + }); + dispatch(setFilterConfiguration(nativeFiltersValues)); + }, [JSON.stringify(scopes), JSON.stringify(dashboardLayout)]); + const childIds: string[] = topLevelTabs ? topLevelTabs.children : [DASHBOARD_GRID_ID]; diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts b/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts index 7560f858f58a2..9d7b84799ba5e 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts +++ b/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts @@ -192,20 +192,15 @@ export const selectNativeIndicatorsForChart = ( const getStatus = ({ value, - isAffectedByScope, column, type = DataMaskType.NativeFilters, }: { value: any; - isAffectedByScope: boolean; column?: string; type?: DataMaskType; }): IndicatorStatus => { // a filter is only considered unset if it's value is null const hasValue = value !== null; - if (!isAffectedByScope) { - return IndicatorStatus.Unset; - } if (type === DataMaskType.CrossFilters && hasValue) { return IndicatorStatus.CrossFilterApplied; } @@ -223,16 +218,18 @@ export const selectNativeIndicatorsForChart = ( let nativeFilterIndicators: any = []; if (isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS)) { - nativeFilterIndicators = Object.values(nativeFilters.filters).map( - nativeFilter => { - const isAffectedByScope = getTreeCheckedItems( - nativeFilter.scope, - dashboardLayout, - ).some( + nativeFilterIndicators = Object.values(nativeFilters.filters) + .filter(nativeFilter => + getTreeCheckedItems(nativeFilter.scope, dashboardLayout).some( layoutItem => dashboardLayout[layoutItem]?.meta?.chartId === chartId, - ); + ), + ) + .map(nativeFilter => { const column = nativeFilter.targets[0]?.column?.name; - let value = dataMask[nativeFilter.id]?.filterState?.value ?? null; + let value = + dataMask[nativeFilter.id]?.filterState?.label ?? + dataMask[nativeFilter.id]?.filterState?.value ?? + null; if (!Array.isArray(value) && value !== null) { value = [value]; } @@ -240,26 +237,28 @@ export const selectNativeIndicatorsForChart = ( column, name: nativeFilter.name, path: [nativeFilter.id], - status: getStatus({ value, isAffectedByScope, column }), + status: getStatus({ value, column }), value, }; - }, - ); + }); } let crossFilterIndicators: any = []; if (isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)) { crossFilterIndicators = Object.values(chartConfiguration) - .map(chartConfig => { - const scope = chartConfig?.crossFilters?.scope; - const isAffectedByScope = getTreeCheckedItems( - scope, + .filter(chartConfig => + getTreeCheckedItems( + chartConfig?.crossFilters?.scope, dashboardLayout, ).some( layoutItem => dashboardLayout[layoutItem]?.meta?.chartId === chartId, - ); - - let value = dataMask[chartConfig.id]?.filterState?.value ?? null; + ), + ) + .map(chartConfig => { + let value = + dataMask[chartConfig.id]?.filterState?.label ?? + dataMask[chartConfig.id]?.filterState?.value ?? + null; if (!Array.isArray(value) && value !== null) { value = [value]; } @@ -270,7 +269,6 @@ export const selectNativeIndicatorsForChart = ( path: [`${chartConfig.id}`], status: getStatus({ value, - isAffectedByScope, type: DataMaskType.CrossFilters, }), value, diff --git a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx index 9b9aefe03b6f6..4ed5fd623d153 100644 --- a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx +++ b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx @@ -168,7 +168,7 @@ class HeaderActionsDropdown extends React.PureComponent { window.location.pathname, getActiveFilters(), window.location.hash, - getUrlParam(URL_PARAMS.standalone, 'number'), + !getUrlParam(URL_PARAMS.standalone), ); window.location.replace(url); break; diff --git a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx index f4868d0e6a08e..102acae84aa02 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx @@ -85,37 +85,46 @@ const defaultProps = { * If ChartHolder were a function component, this could be implemented as a hook instead. */ const FilterFocusHighlight = React.forwardRef( - ({ chartId, focusedFilterScope, ...otherProps }, ref) => { + ({ chartId, focusedFilterScope, nativeFilters, ...otherProps }, ref) => { const theme = useTheme(); - if (!focusedFilterScope) return
; + const focusedNativeFilterId = nativeFilters.focusedFilterId; + if (!(focusedFilterScope || focusedNativeFilterId)) + return
; // we use local styles here instead of a conditionally-applied class, // because adding any conditional class to this container // causes performance issues in Chrome. // default to the "de-emphasized" state - let styles = { opacity: 0.3, pointerEvents: 'none' }; + const unfocusedChartStyles = { opacity: 0.3, pointerEvents: 'none' }; + const focusedChartStyles = { + borderColor: theme.colors.primary.light2, + opacity: 1, + boxShadow: `0px 0px ${theme.gridUnit * 2}px ${ + theme.colors.primary.light2 + }`, + pointerEvents: 'auto', + }; - if ( + if (focusedNativeFilterId) { + if ( + nativeFilters.filters[focusedNativeFilterId].chartsInScope.includes( + chartId, + ) + ) { + return
; + } + } else if ( chartId === focusedFilterScope.chartId || getChartIdsInFilterScope({ filterScope: focusedFilterScope.scope, }).includes(chartId) ) { - // apply the "highlighted" state if this chart - // contains a filter being focused, or is in scope of a focused filter. - styles = { - borderColor: theme.colors.primary.light2, - opacity: 1, - boxShadow: `0px 0px ${theme.gridUnit * 2}px ${ - theme.colors.primary.light2 - }`, - pointerEvents: 'auto', - }; + return
; } // inline styles are used here due to a performance issue when adding/changing a class, which causes a reflow - return
; + return
; }, ); @@ -233,6 +242,7 @@ class ChartHolder extends React.Component { isComponentVisible, dashboardId, focusedFilterScope, + nativeFilters, } = this.props; // inherit the size of parent columns @@ -291,6 +301,7 @@ class ChartHolder extends React.Component { { editMode: false, isComponentVisible: true, dashboardId: 123, + nativeFilters: nativeFiltersInfo.filters, }; const renderWrapper = (props = defaultProps, state = mockState) => diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx index 73cdca34ed155..faa1e046eff7a 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx @@ -18,6 +18,7 @@ */ import React from 'react'; import PropTypes from 'prop-types'; +import { styled } from '@superset-ui/core'; import DashboardComponent from '../../containers/DashboardComponent'; import DragDroppable from '../dnd/DragDroppable'; @@ -62,6 +63,17 @@ const defaultProps = { onResizeStop() {}, }; +const TabTitleContainer = styled.div` + ${({ isHighlighted, theme: { gridUnit, colors } }) => ` + padding: ${gridUnit}px ${gridUnit * 2}px; + margin: ${-gridUnit}px ${gridUnit * -2}px; + transition: box-shadow 0.2s ease-in-out; + ${ + isHighlighted && `box-shadow: 0 0 ${gridUnit}px ${colors.primary.light1};` + } + `} +`; + export default class Tab extends React.PureComponent { constructor(props) { super(props); @@ -192,6 +204,7 @@ export default class Tab extends React.PureComponent { editMode, filters, isFocused, + isHighlighted, } = this.props; return ( @@ -205,7 +218,11 @@ export default class Tab extends React.PureComponent { editMode={editMode} > {({ dropIndicatorProps, dragSourceRef }) => ( -
+ } -
+ )} ); diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx index 427145b89eb18..c41abf83a4b08 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx @@ -31,7 +31,11 @@ import findTabIndexByComponentId from '../../util/findTabIndexByComponentId'; import getDirectPathToTabIndex from '../../util/getDirectPathToTabIndex'; import getLeafComponentIdFromPath from '../../util/getLeafComponentIdFromPath'; import { componentShape } from '../../util/propShapes'; -import { NEW_TAB_ID, DASHBOARD_ROOT_ID } from '../../util/constants'; +import { + NEW_TAB_ID, + DASHBOARD_ROOT_ID, + DASHBOARD_GRID_ID, +} from '../../util/constants'; import { RENDER_TAB, RENDER_TAB_CONTENT } from './Tab'; import { TAB_TYPE } from '../../util/componentTypes'; @@ -268,11 +272,28 @@ class Tabs extends React.PureComponent { renderHoverMenu, isComponentVisible: isCurrentTabVisible, editMode, + nativeFilters, + dashboardLayout, + lastFocusedTabId, + setLastFocusedTab, } = this.props; const { children: tabIds } = tabsComponent; const { tabIndex: selectedTabIndex, activeKey } = this.state; + // On dashboards with top level tabs, set initial focus to the active top level tab + const dashboardRoot = dashboardLayout[DASHBOARD_ROOT_ID]; + const rootChildId = dashboardRoot.children[0]; + const isTopLevelTabs = rootChildId !== DASHBOARD_GRID_ID; + if (isTopLevelTabs && !lastFocusedTabId) { + setLastFocusedTab(activeKey); + } + + let tabsToHighlight; + if (nativeFilters.focusedFilterId) { + tabsToHighlight = + nativeFilters.filters[nativeFilters.focusedFilterId].tabsInScope; + } return ( {tabIds.map((tabId, tabIndex) => ( } > diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tabs.test.tsx b/superset-frontend/src/dashboard/components/gridComponents/Tabs.test.tsx index f35dcd4df10b3..49ec405f25bdf 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Tabs.test.tsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Tabs.test.tsx @@ -20,11 +20,12 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; import { render, screen, waitFor } from 'spec/helpers/testing-library'; +import { nativeFiltersInfo } from 'spec/javascripts/dashboard/fixtures/mockNativeFilters'; import DashboardComponent from 'src/dashboard/containers/DashboardComponent'; import DragDroppable from 'src/dashboard/components/dnd/DragDroppable'; import DeleteComponentButton from 'src/dashboard/components/DeleteComponentButton'; import getLeafComponentIdFromPath from 'src/dashboard/util/getLeafComponentIdFromPath'; - +import emptyDashboardLayout from 'src/dashboard/fixtures/emptyDashboardLayout'; import Tabs from './Tabs'; jest.mock('src/dashboard/containers/DashboardComponent', () => @@ -110,6 +111,8 @@ const createProps = () => ({ onChangeTab: jest.fn(), deleteComponent: jest.fn(), updateComponents: jest.fn(), + dashboardLayout: emptyDashboardLayout, + nativeFilters: nativeFiltersInfo.filters, }); beforeEach(() => { diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CascadeFilters/CascadeFilterControl/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CascadeFilters/CascadeFilterControl/index.tsx index 1ac1fb3a5f5a2..91908823badb2 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CascadeFilters/CascadeFilterControl/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CascadeFilters/CascadeFilterControl/index.tsx @@ -22,8 +22,10 @@ import Icon from 'src/components/Icon'; import FilterControl from 'src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl'; import { CascadeFilter } from 'src/dashboard/components/nativeFilters/FilterBar/CascadeFilters/types'; import { Filter } from 'src/dashboard/components/nativeFilters/types'; +import { DataMaskStateWithId } from 'src/dataMask/types'; export interface CascadeFilterControlProps { + dataMaskSelected?: DataMaskStateWithId; filter: CascadeFilter; directPathToChild?: string[]; onFilterSelectionChange: (filter: Filter, dataMask: DataMask) => void; @@ -45,6 +47,7 @@ const StyledCaretIcon = styled(Icon)` `; const CascadeFilterControl: React.FC = ({ + dataMaskSelected, filter, directPathToChild, onFilterSelectionChange, @@ -53,6 +56,7 @@ const CascadeFilterControl: React.FC = ({ = ({ {filter.cascadeChildren?.map(childFilter => (
  • = ({ + dataMaskSelected, filter, visible, onVisibleChange, @@ -83,9 +82,7 @@ const CascadePopover: React.FC = ({ directPathToChild, }) => { const [currentPathToChild, setCurrentPathToChild] = useState(); - const dataMask = useSelector( - state => state.dataMask[filter.id] ?? getInitialDataMask(filter.id), - ); + const dataMask = dataMaskSelected[filter.id]; useEffect(() => { setCurrentPathToChild(directPathToChild); @@ -98,7 +95,7 @@ const CascadePopover: React.FC = ({ const getActiveChildren = useCallback( (filter: CascadeFilter): CascadeFilter[] | null => { const children = filter.cascadeChildren || []; - const currentValue = dataMask.filterState?.value; + const currentValue = dataMask?.filterState?.value; const activeChildren = children.flatMap( childFilter => getActiveChildren(childFilter) || [], @@ -147,6 +144,7 @@ const CascadePopover: React.FC = ({ if (!filter.cascadeChildren?.length) { return ( = ({ const content = ( = ({
    {activeFilters.map(activeFilter => ( { userEvent.click(screen.getByTestId(getTestId('collapsable'))); userEvent.click(screen.getByTestId(getTestId('create-filter'))); // select filter - userEvent.click(screen.getByText('Select filter')); - userEvent.click(screen.getByText('Time filter')); + userEvent.click(screen.getByText('Value')); + userEvent.click(screen.getByText('Time range')); userEvent.type(screen.getByTestId(getModalTestId('name-input')), FILTER_NAME); userEvent.click(screen.getByText('Save')); await screen.findByText('All Filters (1)'); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx index 157f50e09d449..14dc65b90655e 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx @@ -42,6 +42,7 @@ const StyledFilterControlContainer = styled.div` `; const FilterControl: React.FC = ({ + dataMaskSelected, filter, icon, onFilterSelectionChange, @@ -57,6 +58,7 @@ const FilterControl: React.FC = ({
    {icon}
    theme.gridUnit * 4}px; @@ -33,7 +41,7 @@ const Wrapper = styled.div` type FilterControlsProps = { directPathToChild?: string[]; - dataMaskSelected: DataMaskState; + dataMaskSelected: DataMaskStateWithId; onFilterSelectionChange: (filter: Filter, dataMask: DataMask) => void; }; @@ -44,7 +52,18 @@ const FilterControls: FC = ({ }) => { const [visiblePopoverId, setVisiblePopoverId] = useState(null); const filters = useFilters(); + const dashboardLayout = useDashboardLayout(); + const lastFocusedTabId = useSelector( + state => state.dashboardState?.lastFocusedTabId, + ); const filterValues = Object.values(filters); + const portalNodes = React.useMemo(() => { + const nodes = new Array(filterValues.length); + for (let i = 0; i < filterValues.length; i += 1) { + nodes[i] = portals.createHtmlPortalNode(); + } + return nodes; + }, [filterValues.length]); const cascadeFilters = useMemo(() => { const filtersWithValue = filterValues.map(filter => ({ @@ -53,22 +72,91 @@ const FilterControls: FC = ({ })); return buildCascadeFiltersTree(filtersWithValue); }, [filterValues, dataMaskSelected]); + const cascadeFilterIds = new Set(cascadeFilters.map(item => item.id)); + + let filtersInScope: CascadeFilter[] = []; + const filtersOutOfScope: CascadeFilter[] = []; + const dashboardHasTabs = Object.values(dashboardLayout).some( + element => element.type === TAB_TYPE, + ); + const showCollapsePanel = dashboardHasTabs && cascadeFilters.length > 0; + if (!lastFocusedTabId || !dashboardHasTabs) { + filtersInScope = cascadeFilters; + } else { + cascadeFilters.forEach((filter, index) => { + if (cascadeFilters[index].tabsInScope?.includes(lastFocusedTabId)) { + filtersInScope.push(filter); + } else { + filtersOutOfScope.push(filter); + } + }); + } return ( - {cascadeFilters.map(filter => ( - - setVisiblePopoverId(visible ? filter.id : null) - } - filter={filter} - onFilterSelectionChange={onFilterSelectionChange} - directPathToChild={directPathToChild} - /> - ))} + {portalNodes + .filter((node, index) => cascadeFilterIds.has(filterValues[index].id)) + .map((node, index) => ( + + + setVisiblePopoverId(visible ? cascadeFilters[index].id : null) + } + filter={cascadeFilters[index]} + onFilterSelectionChange={onFilterSelectionChange} + directPathToChild={directPathToChild} + /> + + ))} + {filtersInScope.map(filter => { + const index = filterValues.findIndex(f => f.id === filter.id); + return ; + })} + {showCollapsePanel && ( + css` + &.ant-collapse { + margin-top: ${filtersInScope.length > 0 + ? theme.gridUnit * 6 + : 0}px; + & > .ant-collapse-item { + & > .ant-collapse-header { + padding-left: 0; + padding-bottom: ${theme.gridUnit * 2}px; + + & > .ant-collapse-arrow { + right: ${theme.gridUnit}px; + } + } + + & .ant-collapse-content-box { + padding: ${theme.gridUnit * 4}px 0 0; + } + } + } + `} + > + + {filtersOutOfScope.map(filter => { + const index = cascadeFilters.findIndex(f => f.id === filter.id); + return ; + })} + + + )} ); }; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx index 200edfa35c66d..495f50f437bf4 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx @@ -47,16 +47,20 @@ import { useCascadingFilters } from './state'; const FilterItem = styled.div` min-height: ${({ theme }) => theme.gridUnit * 11}px; padding-bottom: ${({ theme }) => theme.gridUnit * 3}px; + & > div > div { + height: auto; + } `; const FilterValue: React.FC = ({ + dataMaskSelected, filter, directPathToChild, onFilterSelectionChange, }) => { const { id, targets, filterType, adhoc_filters, time_range } = filter; const metadata = getChartMetadataRegistry().get(filterType); - const cascadingFilters = useCascadingFilters(id); + const cascadingFilters = useCascadingFilters(id, dataMaskSelected); const [state, setState] = useState([]); const [error, setError] = useState(''); const [formData, setFormData] = useState>({}); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/state.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/state.ts index 7be5835780895..1aaf772722c16 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/state.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/state.ts @@ -18,22 +18,27 @@ */ import { useSelector } from 'react-redux'; import { NativeFiltersState } from 'src/dashboard/reducers/types'; +import { DataMaskStateWithId } from 'src/dataMask/types'; +import { ExtraFormData } from '@superset-ui/core'; import { mergeExtraFormData } from '../../utils'; -import { useNativeFiltersDataMask } from '../state'; // eslint-disable-next-line import/prefer-default-export -export function useCascadingFilters(id: string) { +export function useCascadingFilters( + id: string, + dataMaskSelected?: DataMaskStateWithId, +): ExtraFormData { const { filters } = useSelector( state => state.nativeFilters, ); const filter = filters[id]; const cascadeParentIds: string[] = filter?.cascadeParentIds ?? []; let cascadedFilters = {}; - const nativeFiltersDataMask = useNativeFiltersDataMask(); cascadeParentIds.forEach(parentId => { - const parentState = nativeFiltersDataMask[parentId] || {}; - const { extraFormData: parentExtra = {} } = parentState; - cascadedFilters = mergeExtraFormData(cascadedFilters, parentExtra); + const parentState = dataMaskSelected?.[parentId]; + cascadedFilters = mergeExtraFormData( + cascadedFilters, + parentState?.extraFormData, + ); }); return cascadedFilters; } diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/types.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/types.ts index 0b39dd210be5e..93bf76d8c2946 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/types.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/types.ts @@ -18,9 +18,11 @@ */ import React from 'react'; import { DataMask } from '@superset-ui/core'; +import { DataMaskStateWithId } from 'src/dataMask/types'; import { Filter } from '../../types'; export interface FilterProps { + dataMaskSelected?: DataMaskStateWithId; filter: Filter & { dataMask?: DataMask; }; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx index 50c16e95018f5..44a4f82aed723 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx @@ -19,7 +19,7 @@ /* eslint-disable no-param-reassign */ import { HandlerFunction, styled, t } from '@superset-ui/core'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; import cx from 'classnames'; import Icon from 'src/components/Icon'; @@ -37,11 +37,7 @@ import { testWithId } from 'src/utils/testUtils'; import { Filter } from 'src/dashboard/components/nativeFilters/types'; import Loading from 'src/components/Loading'; import { getInitialDataMask } from 'src/dataMask/reducer'; -import { - getOnlyExtraFormData, - mapParentFiltersToChildren, - TabIds, -} from './utils'; +import { getOnlyExtraFormData, TabIds } from './utils'; import FilterSets from './FilterSets'; import { useNativeFiltersDataMask, @@ -175,10 +171,6 @@ const FilterBar: React.FC = ({ const filterValues = Object.values(filters); const dataMaskApplied: DataMaskStateWithId = useNativeFiltersDataMask(); const [isFilterSetChanged, setIsFilterSetChanged] = useState(false); - const cascadeChildren = useMemo( - () => mapParentFiltersToChildren(filterValues), - [filterValues], - ); useEffect(() => { setDataMaskSelected(() => dataMaskApplied); @@ -190,15 +182,6 @@ const FilterBar: React.FC = ({ ) => { setIsFilterSetChanged(tab !== TabIds.AllFilters); setDataMaskSelected(draft => { - const children = cascadeChildren[filter.id] || []; - // force instant updating on initialization or for parent filters when dataMaskSelected has filter - if ( - dataMaskSelected[filter.id] && - (filter.isInstant || children.length > 0) - ) { - dispatch(updateDataMask(filter.id, dataMask)); - } - draft[filter.id] = { ...(getInitialDataMask(filter.id) as DataMaskWithId), ...dataMask, diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTabs.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTabs.tsx index bab37da734089..3d687961eaf8e 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTabs.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTabs.tsx @@ -24,7 +24,7 @@ import Icon from 'src/components/Icon'; import { FilterRemoval } from './types'; import { REMOVAL_DELAY_SECS } from './utils'; -export const FILTER_WIDTH = 200; +export const FILTER_WIDTH = 180; export const StyledSpan = styled.span` cursor: pointer; @@ -42,11 +42,9 @@ export const StyledFilterTitle = styled.span` export const StyledAddFilterBox = styled.div` color: ${({ theme }) => theme.colors.primary.dark1}; - text-align: left; - padding: ${({ theme }) => theme.gridUnit * 2}px 0; - margin: ${({ theme }) => theme.gridUnit * 3}px 0 0 - ${({ theme }) => -theme.gridUnit * 2}px; - border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light1}; + padding: ${({ theme }) => theme.gridUnit * 2}px; + border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; + cursor: pointer; &:hover { color: ${({ theme }) => theme.colors.primary.base}; @@ -89,12 +87,19 @@ const FilterTabsContainer = styled(LineEditableTabs)` & > .ant-tabs-content-holder { border-left: 1px solid ${theme.colors.grayscale.light2}; - margin-right: ${theme.gridUnit * 4}px; + padding-right: ${theme.gridUnit * 4}px; + overflow-x: hidden; + overflow-y: auto; } + & > .ant-tabs-content-holder ~ .ant-tabs-content-holder { border: none; } + &.ant-tabs-card > .ant-tabs-nav .ant-tabs-ink-bar { + visibility: hidden; + } + &.ant-tabs-left > .ant-tabs-content-holder > .ant-tabs-content @@ -104,10 +109,13 @@ const FilterTabsContainer = styled(LineEditableTabs)` } .ant-tabs-nav-list { - padding-top: ${theme.gridUnit * 4}px; - padding-right: ${theme.gridUnit * 2}px; - padding-bottom: ${theme.gridUnit * 4}px; + overflow-x: hidden; + overflow-y: auto; + padding-top: ${theme.gridUnit * 2}px; + padding-right: ${theme.gridUnit}px; + padding-bottom: ${theme.gridUnit * 3}px; padding-left: ${theme.gridUnit * 3}px; + width: 270px; } // extra selector specificity: @@ -135,6 +143,24 @@ const FilterTabsContainer = styled(LineEditableTabs)` justify-content: space-between; text-transform: unset; } + + .ant-tabs-nav-more { + display: none; + } + + .ant-tabs-extra-content { + width: 100%; + } + `} +`; + +const StyledHeader = styled.div` + ${({ theme }) => ` + color: ${theme.colors.grayscale.dark1}; + font-size: ${theme.typography.sizes.l}px; + padding-top: ${theme.gridUnit * 4}px; + padding-right: ${theme.gridUnit * 4}px; + padding-left: ${theme.gridUnit * 4}px; `} `; @@ -160,16 +186,37 @@ const FilterTabs: FC = ({ children, }) => ( - {' '} - {t('Add filter')} - - } + hideAdd + tabBarExtraContent={{ + left: {t('Filters')}, + right: ( + { + onEdit('', 'add'); + setTimeout(() => { + const element = document.getElementById('native-filters-tabs'); + if (element) { + const navList = element.getElementsByClassName( + 'ant-tabs-nav-list', + )[0]; + navList.scrollTop = navList.scrollHeight; + } + }, 0); + }} + > + {' '} + + {t('Add filter')} + + + ), + }} > {filterIds.map(id => ( void; +} + +const StyledContainer = styled.div<{ checked: boolean }>` + width: 100%; + display: flex; + flex-direction: column; + align-items: flex-start; + min-height: ${({ theme }) => theme.gridUnit * 10}px; + padding-top: ${({ theme }) => theme.gridUnit * 2 + 2}px; + + .checkbox { + margin-bottom: ${({ theme, checked }) => (checked ? theme.gridUnit : 0)}px; + } + + & > div { + margin-bottom: ${({ theme }) => theme.gridUnit * 2}px; + } +`; + +const CollapsibleControl = (props: CollapsibleControlProps) => { + const { checked = false, title, children, onChange } = props; + const [isChecked, setIsChecked] = useState(checked); + return ( + + { + const value = e.target.checked; + setIsChecked(value); + if (onChange) { + onChange(value); + } + }} + > + {title} + + {isChecked && children} + + ); +}; + +export { CollapsibleControl, CollapsibleControlProps }; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/ControlItems.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/ControlItems.tsx deleted file mode 100644 index ae20a8886035c..0000000000000 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/ControlItems.tsx +++ /dev/null @@ -1,111 +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 { - CustomControlItem, - InfoTooltipWithTrigger, -} from '@superset-ui/chart-controls'; -import React, { FC } from 'react'; -import { Checkbox } from 'src/common/components'; -import { FormInstance } from 'antd/lib/form'; -import { getChartControlPanelRegistry, t } from '@superset-ui/core'; -import { Tooltip } from 'src/components/Tooltip'; -import { getControlItems, setNativeFilterFieldValues } from './utils'; -import { NativeFiltersForm, NativeFiltersFormItem } from '../types'; -import { StyledCheckboxFormItem } from './FiltersConfigForm'; -import { Filter } from '../../types'; - -type ControlItemsProps = { - disabled: boolean; - filterId: string; - forceUpdate: Function; - filterToEdit?: Filter; - form: FormInstance; - formFilter?: NativeFiltersFormItem; -}; - -const ControlItems: FC = ({ - disabled, - forceUpdate, - form, - filterId, - filterToEdit, - formFilter, -}) => { - const filterType = formFilter?.filterType; - - if (!filterType) return null; - - const controlPanelRegistry = getChartControlPanelRegistry(); - const controlItems = - getControlItems(controlPanelRegistry.get(filterType)) ?? []; - return ( - <> - {controlItems - .filter( - (controlItem: CustomControlItem) => - controlItem?.config?.renderTrigger, - ) - .map(controlItem => ( - - - { - if (!controlItem.config.resetConfig) { - forceUpdate(); - return; - } - setNativeFilterFieldValues(form, filterId, { - defaultDataMask: null, - }); - forceUpdate(); - }} - > - {controlItem.config.label}{' '} - {controlItem.config.description && ( - - )} - - - - ))} - - ); -}; -export default ControlItems; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx index 0d05add39e707..524ab83cd79ec 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx @@ -73,6 +73,7 @@ const DefaultValue: FC = ({ chartType={formFilter?.filterType} hooks={{ setDataMask }} enableNoResults={enableNoResults} + filterState={formFilter.defaultDataMask?.filterState} /> ); }; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx index 85e642c87d433..76fa33bdc7097 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx @@ -32,9 +32,17 @@ import { Metric, } from '@superset-ui/chart-controls'; import { FormInstance } from 'antd/lib/form'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { + useCallback, + useEffect, + useState, + useMemo, + forwardRef, + useImperativeHandle, +} from 'react'; import { useSelector } from 'react-redux'; -import { Checkbox, Form, Input } from 'src/common/components'; +import { FormItem } from 'src/components/Form'; +import { Checkbox, Input } from 'src/common/components'; import { Select } from 'src/components/Select'; import SupersetResourceSelect, { cachedSupersetGet, @@ -45,7 +53,6 @@ import { addDangerToast } from 'src/messageToasts/actions'; import { ClientErrorObject } from 'src/utils/getClientErrorObject'; import SelectControl from 'src/explore/components/controls/SelectControl'; import Collapse from 'src/components/Collapse'; -import Button from 'src/components/Button'; import { getChartDataRequest } from 'src/chart/chartAction'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; import { waitForAsyncData } from 'src/middleware/asyncEvent'; @@ -60,10 +67,11 @@ import { import { useBackendFormUpdate } from './state'; import { getFormData } from '../../utils'; import { Filter } from '../../types'; -import ControlItems from './ControlItems'; +import getControlItemsMap from './getControlItemsMap'; import FilterScope from './FilterScope/FilterScope'; import RemovedFilter from './RemovedFilter'; import DefaultValue from './DefaultValue'; +import { CollapsibleControl } from './CollapsibleControl'; import { CASCADING_FILTERS, getFiltersConfigModalTestId, @@ -77,19 +85,42 @@ const StyledContainer = styled.div` justify-content: space-between; `; -const StyledDatasetContainer = styled.div` +const StyledRowContainer = styled.div` display: flex; flex-direction: row; justify-content: space-between; + width: 100%; `; -export const StyledFormItem = styled(Form.Item)` +export const StyledFormItem = styled(FormItem)` width: 49%; margin-bottom: ${({ theme }) => theme.gridUnit * 4}px; + + & .ant-form-item-label { + padding-bottom: 0px; + } + + & .ant-form-item-control-input { + min-height: ${({ theme }) => theme.gridUnit * 10}px; + } `; -export const StyledCheckboxFormItem = styled(Form.Item)` - margin-bottom: 0; +export const StyledRowFormItem = styled(FormItem)` + margin-bottom: 0px; + padding-bottom: 0px; + min-width: 50%; + + & .ant-form-item-label { + padding-bottom: 0px; + } + + .ant-form-item-control-input-content > div > div { + height: auto; + } + + & .ant-form-item-control-input { + min-height: ${({ theme }) => theme.gridUnit * 10}px; + } `; export const StyledLabel = styled.span` @@ -98,7 +129,7 @@ export const StyledLabel = styled.span` text-transform: uppercase; `; -const CleanFormItem = styled(Form.Item)` +const CleanFormItem = styled(FormItem)` margin-bottom: 0; `; @@ -120,6 +151,10 @@ const StyledCollapse = styled(Collapse)` border: 0px; } + .ant-collapse-content-box { + padding-top: ${({ theme }) => theme.gridUnit * 2}px; + } + &.ant-collapse > .ant-collapse-item { border: 0px; border-radius: 0px; @@ -127,9 +162,29 @@ const StyledCollapse = styled(Collapse)` `; const StyledTabs = styled(Tabs)` + .ant-tabs-nav { + position: sticky; + top: 0px; + background: white; + z-index: 1; + } + .ant-tabs-nav-list { padding: 0px; } + + .ant-form-item-label { + padding-bottom: 0px; + } +`; + +const StyledAsterisk = styled.span` + color: ${({ theme }) => theme.colors.error.base}; + font-family: SimSun, sans-serif; + margin-right: ${({ theme }) => theme.gridUnit - 1}px; + &:before { + content: '*'; + } `; const FilterTabs = { @@ -169,24 +224,51 @@ const FILTERS_WITHOUT_COLUMN = [ 'filter_timecolumn', 'filter_groupby', ]; + const FILTERS_WITH_ADHOC_FILTERS = ['filter_select', 'filter_range']; +const BASIC_CONTROL_ITEMS = ['enableEmptyFilter', 'multiSelect']; + +// TODO: Rename the filter plugins and remove this mapping +const FILTER_TYPE_NAME_MAPPING = { + [t('Select filter')]: t('Value'), + [t('Range filter')]: t('Numerical range'), + [t('Time filter')]: t('Time range'), + [t('Time column')]: t('Time column'), + [t('Time grain')]: t('Time grain'), + [t('Group By')]: t('Group by'), +}; + /** * The configuration form for a specific filter. * Assigns field values to `filters[filterId]` in the form. */ -export const FiltersConfigForm: React.FC = ({ - filterId, - filterToEdit, - removed, - restoreFilter, - form, - parentFilters, -}) => { +const FiltersConfigForm = ( + { + filterId, + filterToEdit, + removed, + restoreFilter, + form, + parentFilters, + }: FiltersConfigFormProps, + ref: React.RefObject, +) => { const [metrics, setMetrics] = useState([]); + const [activeTabKey, setActiveTabKey] = useState( + FilterTabs.configuration.key, + ); + const [activeFilterPanelKey, setActiveFilterPanelKey] = useState< + string | string[] + >(FilterPanels.basic.key); + const [hasDefaultValue, setHasDefaultValue] = useState( + !!filterToEdit?.defaultDataMask?.filterState?.value, + ); const forceUpdate = useForceUpdate(); const [datasetDetails, setDatasetDetails] = useState>(); - const formFilter = form.getFieldValue('filters')?.[filterId] || {}; + const defaultFormFilter = useMemo(() => {}, []); + const formFilter = + form.getFieldValue('filters')?.[filterId] || defaultFormFilter; const nativeFilterItems = getChartMetadataRegistry().items; const nativeFilterVizTypes = Object.entries(nativeFilterItems) // @ts-ignore @@ -228,6 +310,12 @@ export const FiltersConfigForm: React.FC = ({ } }, [datasetId, hasColumn]); + useImperativeHandle(ref, () => ({ + changeTab(tab: 'configuration' | 'scoping') { + setActiveTabKey(tab); + }, + })); + const hasMetrics = hasColumn && !!metrics.length; const hasFilledDataset = @@ -243,7 +331,7 @@ export const FiltersConfigForm: React.FC = ({ useBackendFormUpdate(form, filterId); - const refreshHandler = () => { + const refreshHandler = useCallback(() => { if (!hasDataset || !formFilter?.dataset?.value) { forceUpdate(); return; @@ -287,7 +375,7 @@ export const FiltersConfigForm: React.FC = ({ forceUpdate(); } }); - }; + }, [filterId, forceUpdate, form, formFilter, hasDataset]); const defaultDatasetSelectOptions = Object.values(loadedDatasets).map( datasetToSelectOption, @@ -304,6 +392,19 @@ export const FiltersConfigForm: React.FC = ({ ...formFilter, }); + useEffect(() => { + if (hasDataset && hasFilledDataset && hasDefaultValue && isDataDirty) { + refreshHandler(); + } + }, [ + hasDataset, + hasFilledDataset, + hasDefaultValue, + formFilter, + isDataDirty, + refreshHandler, + ]); + const onDatasetSelectError = useCallback( ({ error, message }: ClientErrorObject) => { let errorText = message || error || t('An error has occurred'); @@ -315,301 +416,463 @@ export const FiltersConfigForm: React.FC = ({ [], ); - if (removed) { - return restoreFilter(filterId)} />; - } - const parentFilterOptions = parentFilters.map(filter => ({ value: filter.id, label: filter.title, })); + const parentFilter = parentFilterOptions.find( + ({ value }) => value === filterToEdit?.cascadeParentIds[0], + ); + + const hasParentFilter = !!parentFilter; + + const hasPreFilter = + !!filterToEdit?.adhoc_filters || !!filterToEdit?.time_range; + + const hasSorting = + typeof filterToEdit?.controlValues?.sortAscending === 'boolean'; + const showDefaultValue = !hasDataset || (!isDataDirty && hasFilledDataset); + const controlItems = formFilter + ? getControlItemsMap({ + disabled: false, + forceUpdate, + form, + filterId, + filterType: formFilter.filterType, + filterToEdit, + }) + : {}; + + const onSortChanged = (value: boolean | undefined) => { + const previous = form.getFieldValue('filters')?.[filterId].controlValues; + setNativeFilterFieldValues(form, filterId, { + controlValues: { + ...previous, + sortAscending: value, + }, + }); + forceUpdate(); + }; + + let hasCheckedAdvancedControl = hasParentFilter || hasPreFilter || hasSorting; + if (!hasCheckedAdvancedControl) { + hasCheckedAdvancedControl = Object.keys(controlItems) + .filter(key => !BASIC_CONTROL_ITEMS.includes(key)) + .some(key => controlItems[key].checked); + } + + useEffect(() => { + const activeFilterPanelKey = [FilterPanels.basic.key]; + if (hasCheckedAdvancedControl) { + activeFilterPanelKey.push(FilterPanels.advanced.key); + } + setActiveFilterPanelKey(activeFilterPanelKey); + }, [hasCheckedAdvancedControl]); + + if (removed) { + return restoreFilter(filterId)} />; + } + return ( - <> - - - - {t('Filter name')}} - initialValue={filterToEdit?.name} - rules={[{ required: !removed, message: t('Name is required') }]} - > - - + setActiveTabKey(activeKey)} + centered + > + + + {t('Filter name')}} + initialValue={filterToEdit?.name} + rules={[{ required: !removed, message: t('Name is required') }]} + > + + + {t('Filter Type')}} + {...getFiltersConfigModalTestId('filter-type')} + > + ({ - value: filterType, - // @ts-ignore - label: nativeFilterItems[filterType]?.value.name, - }))} - onChange={({ value }: { value: string }) => { - setNativeFilterFieldValues(form, filterId, { - filterType: value, - defaultDataMask: null, - }); + { + // We need reset column when dataset changed + if (datasetId && e?.value !== datasetId) { + setNativeFilterFieldValues(form, filterId, { + defaultDataMask: null, + column: null, + }); + } forceUpdate(); }} /> - - {hasDataset && ( - + {hasColumn && ( {t('Dataset')}} + // don't show the column select unless we have a dataset + // style={{ display: datasetId == null ? undefined : 'none' }} + name={['filters', filterId, 'column']} + initialValue={initColumn} + label={{t('Column')}} rules={[ - { required: !removed, message: t('Dataset is required') }, + { required: !removed, message: t('Field is required') }, ]} - {...getFiltersConfigModalTestId('datasource-input')} + data-test="field-input" > - { - // We need reset column when dataset changed - if (datasetId && e?.value !== datasetId) { - setNativeFilterFieldValues(form, filterId, { - defaultDataMask: null, - column: null, - }); - } + { + // We need reset default value when when column changed + setNativeFilterFieldValues(form, filterId, { + defaultDataMask: null, + }); forceUpdate(); }} /> - {hasColumn && ( - {t('Column')}} - rules={[ - { required: !removed, message: t('Field is required') }, - ]} - data-test="field-input" - > - { - // We need reset default value when when column changed + )} + + )} + setActiveFilterPanelKey(key)} + expandIconPosition="right" + > + + {hasFilledDataset && ( + - )} - - )} - + ) : ( + t('Fill all required fields to enable "Default Value"') + )} + + + {Object.keys(controlItems) + .filter(key => BASIC_CONTROL_ITEMS.includes(key)) + .map(key => controlItems[key].element)} + + + {t('Apply changes instantly')} + + + + {((hasDataset && hasAdditionalFilters) || hasMetrics) && ( - {hasFilledDataset && ( - - {((hasDataset && hasAdditionalFilters) || hasMetrics) && ( - - {hasDataset && hasAdditionalFilters && ( - <> + + {t('Time range')}} + initialValue={filterToEdit?.time_range || 'No filter'} + > + { + setNativeFilterFieldValues(form, filterId, { + time_range: timeRange, + }); + forceUpdate(); + }} + /> + + + )} + {formFilter?.filterType !== 'filter_range' && ( + onSortChanged(checked || undefined)} + checked={hasSorting} + > + {t('Sort type')}} > - c.filterable, - ) || [] + -

    {label}

    - - ); - })} + ).map(field => + FORM_FIELD_MAP[field]({ + required: parameters.required.includes(field), + changeMethods: { onParametersChange, onChange }, + validationErrors, + getValidation, + key: field, + }), + )}
    ); - export const FormFieldMap = FORM_FIELD_MAP; export default DatabaseConnectionForm; diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ExtraOptions.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ExtraOptions.tsx index 174ef60930471..c046c25011115 100644 --- a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ExtraOptions.tsx +++ b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ExtraOptions.tsx @@ -292,12 +292,12 @@ const ExtraOptions = ({ checked={!!db?.impersonate_user} onChange={onInputChange} labelText={t( - 'Impersonate Logged In User (Presto, Hive, and GSheets)', + 'Impersonate Logged In User (Presto, Trino, Hive, and GSheets)', )} /> | null { const trimmedState = { ...(state || {}), - database_name: state?.database_name?.trim() || '', - sqlalchemy_uri: state?.sqlalchemy_uri || '', }; switch (action.type) { @@ -163,9 +161,7 @@ function dbReducer( }; case ActionType.fetched: return { - parameters: { - engine: trimmedState.parameters?.engine, - }, + engine: trimmedState.engine, configuration_method: trimmedState.configuration_method, ...action.payload, }; @@ -196,13 +192,16 @@ const DatabaseModal: FunctionComponent = ({ >(dbReducer, null); const [tabKey, setTabKey] = useState(DEFAULT_TAB_KEY); const [availableDbs, getAvailableDbs] = useAvailableDatabases(); + const [validationErrors, getValidation] = useDatabaseValidation(); const [hasConnectedDb, setHasConnectedDb] = useState(false); + const [dbName, setDbName] = useState(''); const conf = useCommonConf(); const isEditMode = !!databaseId; const useSqlAlchemyForm = db?.configuration_method === CONFIGURATION_METHOD.SQLALCHEMY_URI; const useTabLayout = isEditMode || useSqlAlchemyForm; + // Database fetch logic const { state: { loading: dbLoading, resource: dbFetched }, @@ -248,14 +247,16 @@ const DatabaseModal: FunctionComponent = ({ // don't pass parameters if using the sqlalchemy uri delete update.parameters; } - updateResource(db.id as number, update as DatabaseObject).then(result => { - if (result) { - if (onDatabaseAdd) { - onDatabaseAdd(); - } - onClose(); + const result = await updateResource( + db.id as number, + update as DatabaseObject, + ); + if (result) { + if (onDatabaseAdd) { + onDatabaseAdd(); } - }); + onClose(); + } } else if (db) { // Create const dbId = await createResource(update as DatabaseObject); @@ -300,7 +301,6 @@ const DatabaseModal: FunctionComponent = ({ setDB({ type: ActionType.dbSelected, payload: { - parameters: {}, configuration_method: CONFIGURATION_METHOD.SQLALCHEMY_URI, }, // todo hook this up to step 1 }); @@ -316,6 +316,9 @@ const DatabaseModal: FunctionComponent = ({ type: ActionType.fetched, payload: dbFetched, }); + // keep a copy of the name separate for display purposes + // because it shouldn't change when the form is updated + setDbName(dbFetched.database_name); } }, [dbFetched]); @@ -326,7 +329,7 @@ const DatabaseModal: FunctionComponent = ({ const dbModel: DatabaseForm = availableDbs?.databases?.find( (available: { engine: string | undefined }) => - available.engine === db?.parameters?.engine, + available.engine === db?.engine, ) || {}; const disableSave = @@ -362,12 +365,12 @@ const DatabaseModal: FunctionComponent = ({ } > {isEditMode ? ( - + {db?.backend} - {db?.database_name} - + {dbName} + ) : ( - + Enter Primary Credentials Need help? Learn how to connect your database{' '} @@ -380,7 +383,7 @@ const DatabaseModal: FunctionComponent = ({ . - + )}
    = ({ value: target.value, }) } + getValidation={() => getValidation(db)} + validationErrors={validationErrors} />