diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 089473a3a3f8..102c158b986d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -322,6 +322,10 @@ updates: - package-ecosystem: "npm" directory: "/superset-frontend/packages/superset-ui-core/" + ignore: + # not until React >= 18.0.0 + - dependency-name: "react-markdown" + - dependency-name: "remark-gfm" schedule: interval: "monthly" labels: diff --git a/.github/workflows/ephemeral-env.yml b/.github/workflows/ephemeral-env.yml index 4c4747f5e2ce..4c503ed6fe1e 100644 --- a/.github/workflows/ephemeral-env.yml +++ b/.github/workflows/ephemeral-env.yml @@ -50,17 +50,45 @@ jobs: echo "result=up" >> $GITHUB_OUTPUT else echo "result=noop" >> $GITHUB_OUTPUT - exit 1 fi - name: Get event SHA id: get-sha - run: | - echo "sha=${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT + if: steps.eval-label.outputs.result == 'up' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + let prSha; + + // If event is workflow_dispatch, use the issue_number from inputs + if (context.eventName === "workflow_dispatch") { + const prNumber = "${{ github.event.inputs.issue_number }}"; + if (!prNumber) { + console.log("No PR number found."); + return; + } + + // Fetch PR details using the provided issue_number + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + + prSha = pr.head.sha; + } else { + // If it's not workflow_dispatch, use the PR head sha from the event + prSha = context.payload.pull_request.head.sha; + } + + console.log(`PR SHA: ${prSha}`); + core.setOutput("sha", prSha); - name: Looking for feature flags in PR description uses: actions/github-script@v7 id: eval-feature-flags + if: steps.eval-label.outputs.result == 'up' with: script: | const description = context.payload.pull_request @@ -81,6 +109,7 @@ jobs: - name: Reply with confirmation comment uses: actions/github-script@v7 + if: steps.eval-label.outputs.result == 'up' with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -161,8 +190,9 @@ jobs: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY: superset-ci IMAGE_TAG: apache/superset:${{ needs.ephemeral-env-label.outputs.sha }}-ci + PR_NUMBER: ${{ github.event.inputs.issue_number || github.event.pull_request.number }} run: | - docker tag $IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci + docker tag $IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:pr-$PR_NUMBER-ci docker push -a $ECR_REGISTRY/$ECR_REPOSITORY ephemeral-env-up: @@ -193,11 +223,13 @@ jobs: - name: Check target image exists in ECR id: check-image continue-on-error: true + env: + PR_NUMBER: ${{ github.event.inputs.issue_number || github.event.pull_request.number }} run: | aws ecr describe-images \ --registry-id $(echo "${{ steps.login-ecr.outputs.registry }}" | grep -Eo "^[0-9]+") \ --repository-name superset-ci \ - --image-ids imageTag=pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci + --image-ids imageTag=pr-$PR_NUMBER-ci - name: Fail on missing container image if: steps.check-image.outcome == 'failure' @@ -207,7 +239,7 @@ jobs: script: | const errMsg = '@${{ github.event.comment.user.login }} Container image not yet published for this PR. Please try again when build is complete.'; github.rest.issues.createComment({ - issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }}, + issue_number: ${{ github.event.inputs.issue_number || github.event.pull_request.number }}, owner: context.repo.owner, repo: context.repo.repo, body: errMsg @@ -220,7 +252,7 @@ jobs: with: task-definition: .github/workflows/ecs-task-definition.json container-name: superset-ci - image: ${{ steps.login-ecr.outputs.registry }}/superset-ci:pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci + image: ${{ steps.login-ecr.outputs.registry }}/superset-ci:pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-ci - name: Update env vars in the Amazon ECS task definition run: | @@ -229,29 +261,30 @@ jobs: - name: Describe ECS service id: describe-services run: | - echo "active=$(aws ecs describe-services --cluster superset-ci --services pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service | jq '.services[] | select(.status == "ACTIVE") | any')" >> $GITHUB_OUTPUT + echo "active=$(aws ecs describe-services --cluster superset-ci --services pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service | jq '.services[] | select(.status == "ACTIVE") | any')" >> $GITHUB_OUTPUT - name: Create ECS service id: create-service if: steps.describe-services.outputs.active != 'true' env: ECR_SUBNETS: subnet-0e15a5034b4121710,subnet-0e8efef4a72224974 ECR_SECURITY_GROUP: sg-092ff3a6ae0574d91 + PR_NUMBER: ${{ github.event.inputs.issue_number || github.event.pull_request.number }} run: | aws ecs create-service \ --cluster superset-ci \ - --service-name pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service \ + --service-name pr-$PR_NUMBER-service \ --task-definition superset-ci \ --launch-type FARGATE \ --desired-count 1 \ --platform-version LATEST \ --network-configuration "awsvpcConfiguration={subnets=[$ECR_SUBNETS],securityGroups=[$ECR_SECURITY_GROUP],assignPublicIp=ENABLED}" \ - --tags key=pr,value=${{ github.event.inputs.issue_number || github.event.issue.number }} key=github_user,value=${{ github.actor }} + --tags key=pr,value=$PR_NUMBER key=github_user,value=${{ github.actor }} - name: Deploy Amazon ECS task definition id: deploy-task uses: aws-actions/amazon-ecs-deploy-task-definition@v2 with: task-definition: ${{ steps.task-def.outputs.task-definition }} - service: pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service + service: pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service cluster: superset-ci wait-for-service-stability: true wait-for-minutes: 10 @@ -259,7 +292,7 @@ jobs: - name: List tasks id: list-tasks run: | - echo "task=$(aws ecs list-tasks --cluster superset-ci --service-name pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service | jq '.taskArns | first')" >> $GITHUB_OUTPUT + echo "task=$(aws ecs list-tasks --cluster superset-ci --service-name pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service | jq '.taskArns | first')" >> $GITHUB_OUTPUT - name: Get network interface id: get-eni run: | @@ -274,20 +307,22 @@ jobs: with: github-token: ${{github.token}} script: | + const issue_number = context.payload.inputs?.issue_number || context.issue.number; github.rest.issues.createComment({ - issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }}, + issue_number: issue_number, owner: context.repo.owner, repo: context.repo.repo, - body: '@${{ github.actor }} Ephemeral environment spinning up at http://${{ steps.get-ip.outputs.ip }}:8080. Credentials are `admin`/`admin`. Please allow several minutes for bootstrapping and startup.' - }) + body: `@${{ github.actor }} Ephemeral environment spinning up at http://${{ steps.get-ip.outputs.ip }}:8080. Credentials are 'admin'/'admin'. Please allow several minutes for bootstrapping and startup.` + }); - name: Comment (failure) if: ${{ failure() }} uses: actions/github-script@v7 with: github-token: ${{github.token}} script: | + const issue_number = context.payload.inputs?.issue_number || context.issue.number; github.rest.issues.createComment({ - issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }}, + issue_number: issue_number, owner: context.repo.owner, repo: context.repo.repo, body: '@${{ github.event.inputs.user_login || github.event.comment.user.login }} Ephemeral environment creation failed. Please check the Actions logs for details.' diff --git a/CHANGELOG.md b/CHANGELOG.md index f00ba2d39f00..9e8f17c823d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,3 +44,4 @@ under the License. - [4.0.1](./CHANGELOG/4.0.1.md) - [4.0.2](./CHANGELOG/4.0.2.md) - [4.1.0](./CHANGELOG/4.1.0.md) +- [5.0.0](./CHANGELOG/5.0.0.md) diff --git a/CHANGELOG/5.0.0.md b/CHANGELOG/5.0.0.md new file mode 100644 index 000000000000..fe68f211ef77 --- /dev/null +++ b/CHANGELOG/5.0.0.md @@ -0,0 +1,937 @@ + + +## Change Log + +### 5.0.0 (Wed Jun 18 13:54:10 2025 -0300) + +**Database Migrations** + +- [#31959](https://github.com/apache/superset/pull/31959) refactor: upload data unification, less permissions and less endpoints (@dpgaspar) +- [#31582](https://github.com/apache/superset/pull/31582) refactor: Removes 5.0 approved legacy charts (@michael-s-molina) +- [#31490](https://github.com/apache/superset/pull/31490) feat: use docker in frontend GHA to parallelize work (@mistercrunch) +- [#30398](https://github.com/apache/superset/pull/30398) feat: add and use UUIDMixin for most models (@mistercrunch) +- [#29649](https://github.com/apache/superset/pull/29649) fix: remove old database constraint on the Dataset model (@betodealmeida) +- [#31447](https://github.com/apache/superset/pull/31447) chore: enforce more ruff rules (@mistercrunch) +- [#31303](https://github.com/apache/superset/pull/31303) feat: Adds helper functions for migrations (@luizotavio32) + +**Features** + +- [#32052](https://github.com/apache/superset/pull/32052) feat: add connector for Parseable (@AdheipSingh) +- [#32051](https://github.com/apache/superset/pull/32051) feat(sqllab): improve table metadata UI (@justinpark) +- [#29900](https://github.com/apache/superset/pull/29900) feat(sqllab): Replace FilterableTable by AgGrid Table (@justinpark) +- [#31979](https://github.com/apache/superset/pull/31979) feat(fe): upgrade `superset-frontend` to Typescript v5 (@hainenber) +- [#31413](https://github.com/apache/superset/pull/31413) feat: add date format to the email subject (@US579) +- [#31984](https://github.com/apache/superset/pull/31984) feat: run prettier before eslint in pre-commit hooks (@mistercrunch) +- [#31889](https://github.com/apache/superset/pull/31889) feat(CalendarFrame): adding previous calendar quarter (@alexandrusoare) +- [#31796](https://github.com/apache/superset/pull/31796) feat: get docker-compose to work as the backend for Cypress tests (@mistercrunch) +- [#31876](https://github.com/apache/superset/pull/31876) feat: use npm run dev-server in docker-compose (@mistercrunch) +- [#31849](https://github.com/apache/superset/pull/31849) feat: old Firebolt dialect (@betodealmeida) +- [#31840](https://github.com/apache/superset/pull/31840) feat: Mutate SQL query executed by alerts (@Vitor-Avila) +- [#31825](https://github.com/apache/superset/pull/31825) feat: Firebolt sqlglot dialect (@betodealmeida) +- [#31575](https://github.com/apache/superset/pull/31575) feat: redesign labels (@mistercrunch) +- [#31747](https://github.com/apache/superset/pull/31747) feat: improve docker-compose services boot sequence (@mistercrunch) +- [#31760](https://github.com/apache/superset/pull/31760) feat: allowing print() statements to be unbuffered in docker (@mistercrunch) +- [#31486](https://github.com/apache/superset/pull/31486) feat: push predicates into virtual datasets (@betodealmeida) +- [#31518](https://github.com/apache/superset/pull/31518) feat: adds a github action to auto label draft prs (@sadpandajoe) +- [#31740](https://github.com/apache/superset/pull/31740) feat: make CI against 'next' python version not-required (@mistercrunch) +- [#31602](https://github.com/apache/superset/pull/31602) feat(Sqllab): Enabling selection and copying of columns and rows in sql lab and dataset view (@samraHanif0340) +- [#31580](https://github.com/apache/superset/pull/31580) feat(doris): add catalog support for Apache Doris (@liujiwen-up) +- [#25869](https://github.com/apache/superset/pull/25869) feat(plugin): add plugin-chart-cartodiagram (@jansule) +- [#31037](https://github.com/apache/superset/pull/31037) feat(country-map): add map for France with all overseas territories (@tarraschk) +- [#31386](https://github.com/apache/superset/pull/31386) feat(gha): various docker / docker-compose build improvements (@mistercrunch) +- [#31316](https://github.com/apache/superset/pull/31316) feat(sqllab): giving the query history pane a facelift (@mistercrunch) +- [#31273](https://github.com/apache/superset/pull/31273) feat: fine-grain chart data telemetry (@betodealmeida) +- [#31141](https://github.com/apache/superset/pull/31141) feat: add YDB as a new database engine (@vgvoleg) +- [#31261](https://github.com/apache/superset/pull/31261) feat(Handlebars): formatNumber and group helpers (@Vitor-Avila) +- [#31260](https://github.com/apache/superset/pull/31260) feat: use uv in CI (@mistercrunch) +- [#31187](https://github.com/apache/superset/pull/31187) feat(sqllab): Popup notification when download data can exceed row count (@justinpark) +- [#31166](https://github.com/apache/superset/pull/31166) feat: make sure to quote formulas on Excel export (@betodealmeida) +- [#31164](https://github.com/apache/superset/pull/31164) feat: purge OAuth2 tokens when DB changes (@betodealmeida) +- [#30870](https://github.com/apache/superset/pull/30870) feat: make ephemeral env use supersetbot + deprecate build_docker.py (@mistercrunch) +- [#30926](https://github.com/apache/superset/pull/30926) feat(trino,presto): add missing time grains (@villebro) +- [#30884](https://github.com/apache/superset/pull/30884) feat: add logging durations for screenshot async service (@mistercrunch) +- [#29609](https://github.com/apache/superset/pull/29609) feat: add a script to check environment software versions (@mistercrunch) +- [#30081](https://github.com/apache/superset/pull/30081) feat(oauth2): add support for trino (@joaoferrao) +- [#30694](https://github.com/apache/superset/pull/30694) feat: allow exporting all tabs to a single PDF in report (@US579) +- [#30674](https://github.com/apache/superset/pull/30674) feat(oauth): adding necessary changes to support bigquery oauth (@fisjac) +- [#30721](https://github.com/apache/superset/pull/30721) feat(dataset API): Add parameter to optionally render Jinja macros in API response (@Vitor-Avila) +- [#30412](https://github.com/apache/superset/pull/30412) feat: cancel impala query on stop (@wugeer) +- [#30710](https://github.com/apache/superset/pull/30710) feat(helm-chart): Add extraLabels to all resources (@maxforasteiro) +- [#29927](https://github.com/apache/superset/pull/29927) feat(db_engine_specs): added support for Denodo Virtual DataPort (@denodo-research-labs) +- [#30593](https://github.com/apache/superset/pull/30593) feat(number-format): Add duration formatter with colon notation (@gerbermichi) +- [#30559](https://github.com/apache/superset/pull/30559) feat(formatting): Add memory units adaptive formatter to format bytes (@mkopec87) +- [#30501](https://github.com/apache/superset/pull/30501) feat(SQL Lab): better SQL parsing error messages (@betodealmeida) +- [#30390](https://github.com/apache/superset/pull/30390) feat(be/cfg): replace deprecated imp.load_source with importlib.util (@hainenber) +- [#29395](https://github.com/apache/superset/pull/29395) feat(dashboard): update tab drag and drop reordering with positional placement and indicators for UI (@rtexelm) +- [#30380](https://github.com/apache/superset/pull/30380) feat(auth): when user is not logged in, failure to access a dashboard should redirect to login screen (@sfirke) +- [#30364](https://github.com/apache/superset/pull/30364) feat(datasets): Allow swap dataset after deletion (@Antonio-RiveroMartnez) +- [#30336](https://github.com/apache/superset/pull/30336) feat(Digest): Add RLS at digest generation for Charts and Dashboards (@geido) +- [#30266](https://github.com/apache/superset/pull/30266) feat: allow configuring an engine context manager (@betodealmeida) +- [#30323](https://github.com/apache/superset/pull/30323) feat(jinja): add option to format time filters using strftime (@villebro) +- [#29897](https://github.com/apache/superset/pull/29897) feat(explore): Add time shift color control to ECharts (@rtexelm) +- [#30016](https://github.com/apache/superset/pull/30016) feat: Displaying details to Dataset/Database deletion modals (@rusackas) +- [#30142](https://github.com/apache/superset/pull/30142) feat(jinja): add advanced temporal filter functionality (@villebro) +- [#28110](https://github.com/apache/superset/pull/28110) feat(db_engine): Implement user impersonation support for StarRocks (@Woellchen) +- [#30126](https://github.com/apache/superset/pull/30126) feat: OAuth2 database field (@betodealmeida) +- [#30082](https://github.com/apache/superset/pull/30082) feat: Oauth2 in DatabaseSelector (@betodealmeida) +- [#30071](https://github.com/apache/superset/pull/30071) feat: allow create/update OAuth2 DB (@betodealmeida) +- [#29912](https://github.com/apache/superset/pull/29912) feat(GAQ): Add Redis Sentinel Support for Global Async Queries (@nsivarajan) +- [#24308](https://github.com/apache/superset/pull/24308) feat(docker): add GUNICORN_LOGLEVEL env var (@drummerwolli) +- [#29333](https://github.com/apache/superset/pull/29333) feat(alert/reports): adding logic to handle downstream reports when tab is deleted from dashboard (@fisjac) +- [#30002](https://github.com/apache/superset/pull/30002) feat(time_comparison): Support all date formats when computing custom and inherit offsets (@Antonio-RiveroMartnez) +- [#25775](https://github.com/apache/superset/pull/25775) feat: Adding Elestio as deployment option (@kaiwalyakoparkar) +- [#29941](https://github.com/apache/superset/pull/29941) feat(docs): fix bug google chrome < 114 not found (@hoalongnatsu) +- [#29917](https://github.com/apache/superset/pull/29917) feat: Enable injecting custom html into head (@kgabryje) +- [#29875](https://github.com/apache/superset/pull/29875) feat(build): webpack visualizer (@rusackas) +- [#29724](https://github.com/apache/superset/pull/29724) feat: get html (links/styling/img/...) to work in pivot table (@mistercrunch) +- [#29795](https://github.com/apache/superset/pull/29795) feat: adding AntdThemeProvider to storybook config (@rusackas) +- [#29096](https://github.com/apache/superset/pull/29096) feat(alerts): enable tab selection for dashboard alerts/reports (@fisjac) +- [#29553](https://github.com/apache/superset/pull/29553) feat(explorer): Add configs and formatting to discrete comparison columns (@rtexelm) +- [#29627](https://github.com/apache/superset/pull/29627) feat(country map): Adding Hungary (and other touchups) (@rusackas) + +**Fixes** + +- [#33817](https://github.com/apache/superset/pull/33817) fix: SQL Lab warning message sizes (@michael-s-molina) +- [#33779](https://github.com/apache/superset/pull/33779) fix(Echarts): Echarts Legend Scroll fix (@amaannawab923) +- [#33765](https://github.com/apache/superset/pull/33765) fix(tooltip): Sanitize tooltip html (@msyavuz) +- [#33759](https://github.com/apache/superset/pull/33759) fix: apply d3 format to BigNumber(s) (@betodealmeida) +- [#33752](https://github.com/apache/superset/pull/33752) fix(create chart page): add missing space between words (@Quatters) +- [#33748](https://github.com/apache/superset/pull/33748) fix: sync dot color between dashboard chart and edit chart (@anantaoutlook) +- [#33743](https://github.com/apache/superset/pull/33743) fix(dataset): Fix plural toast messages (@rad-pat) +- [#33717](https://github.com/apache/superset/pull/33717) fix(explore): add gap to the "Cached" button (@Quatters) +- [#33719](https://github.com/apache/superset/pull/33719) fix(Alerts & reports): invalid "Last updated" time formatting (@Quatters) +- [#33726](https://github.com/apache/superset/pull/33726) fix(dashboard): show dashboard thumbnail images when retrieved (@rad-pat) +- [#33296](https://github.com/apache/superset/pull/33296) fix(template_processing): get_filters now works for IS_NULL and IS_NOT_NULL operators (@Prokos) +- [#32414](https://github.com/apache/superset/pull/32414) fix(api): Added uuid to list api calls (@withnale) +- [#33710](https://github.com/apache/superset/pull/33710) fix: Migrate charts with empty query_context (@luizotavio32) +- [#33592](https://github.com/apache/superset/pull/33592) fix: Makes time compare migration more resilient (@michael-s-molina) +- [#33596](https://github.com/apache/superset/pull/33596) fix: Missing processor context when rendering Jinja (@michael-s-molina) +- [#33285](https://github.com/apache/superset/pull/33285) fix: Adjust viz migrations to also migrate the queries object (@luizotavio32) +- [#33431](https://github.com/apache/superset/pull/33431) fix(sankey): incorrect nodeValues (@richardfogaca) +- [#33553](https://github.com/apache/superset/pull/33553) fix(AllEntities): Display action buttons according to the user permissions (@Vitor-Avila) +- [#30577](https://github.com/apache/superset/pull/30577) fix(user settings): Update forked cosmo theme to resolve down chevron in caret style (#30514) (@mklumpen) +- [#33540](https://github.com/apache/superset/pull/33540) fix(table): table sort by fix (@amaannawab923) +- [#33522](https://github.com/apache/superset/pull/33522) fix(Sqllab): Autocomplete got stuck in UI when open it too fast (@rebenitez1802) +- [#33444](https://github.com/apache/superset/pull/33444) fix: allow metadata to parse json (@eschutho) +- [#33425](https://github.com/apache/superset/pull/33425) fix(table-chart): time shift is not working (@justinpark) +- [#33364](https://github.com/apache/superset/pull/33364) fix(deckgl): fix deckgl multiple layers chart filter and viewport (@syedbarimanjan) +- [#33422](https://github.com/apache/superset/pull/33422) fix(Row): don't unload charts while embedded to reduce rerenders (@msyavuz) +- [#33354](https://github.com/apache/superset/pull/33354) fix: loading examples from raw.githubusercontent.com fails with 429 errors (@mistercrunch) +- [#31917](https://github.com/apache/superset/pull/31917) fix(be/utils): sync cache timeout for memoized function (@hainenber) +- [#33345](https://github.com/apache/superset/pull/33345) fix(i18n): zh_TW pybabel compile error: placeholders are incompatible (@bestlong) +- [#33337](https://github.com/apache/superset/pull/33337) fix: Edge case with metric not getting quoted in sort by when normalize_columns is enabled (@Vitor-Avila) +- [#33224](https://github.com/apache/superset/pull/33224) fix: Temporal filter conversion in viz migrations (@michael-s-molina) +- [#33306](https://github.com/apache/superset/pull/33306) fix: improve function detection (@betodealmeida) +- [#33269](https://github.com/apache/superset/pull/33269) fix(echarts): rename time series shifted colnames (@justinpark) +- [#33267](https://github.com/apache/superset/pull/33267) fix: mask password on DB import (@betodealmeida) +- [#33025](https://github.com/apache/superset/pull/33025) fix: LocalProxy is not mapped warning (@dpgaspar) +- [#33248](https://github.com/apache/superset/pull/33248) fix(histogram): remove extra single quotes (@rusackas) +- [#33250](https://github.com/apache/superset/pull/33250) fix(DB update): Gracefully handle querry error during DB update (@Vitor-Avila) +- [#33238](https://github.com/apache/superset/pull/33238) fix(heatmap): correctly render int and boolean falsy values on axes (@sfirke) +- [#33237](https://github.com/apache/superset/pull/33237) fix(sqllab permalink): Commit SQL Lab permalinks (@Vitor-Avila) +- [#33234](https://github.com/apache/superset/pull/33234) fix(standalone): Ensure correct URL param value for standalone mode (@Vitor-Avila) +- [#33291](https://github.com/apache/superset/pull/33291) fix(antd): Invalid dashed border in tertiary button (@justinpark) +- [#33214](https://github.com/apache/superset/pull/33214) fix(export): Full CSV/Excel exports respecting SQL_MAX_ROW config (@Vitor-Avila) +- [#33164](https://github.com/apache/superset/pull/33164) fix(sqllab): Invalid SQL Error breaks SQL Lab (@justinpark) +- [#33154](https://github.com/apache/superset/pull/33154) fix(deckgl): Update Arc to properly adjust line width (@rusackas) +- [#33161](https://github.com/apache/superset/pull/33161) fix: os.makedirs race condition (@jamra) +- [#33143](https://github.com/apache/superset/pull/33143) fix(echart): Thrown errors shown after resized (@justinpark) +- [#33138](https://github.com/apache/superset/pull/33138) fix(echart): Tooltip date format doesn't follow time grain (@justinpark) +- [#31692](https://github.com/apache/superset/pull/31692) fix(lang): patch FAB's LocaleView to redirect to previous page (@pomegranited) +- [#33106](https://github.com/apache/superset/pull/33106) fix(dashboard): invalid active tab state (@justinpark) +- [#33037](https://github.com/apache/superset/pull/33037) fix: Viz migration error handling (@michael-s-molina) +- [#33107](https://github.com/apache/superset/pull/33107) fix(playwright): allow screenshotting empty dashboards (@hxtmdev) +- [#33110](https://github.com/apache/superset/pull/33110) fix: resolve recent merge collisio (@mistercrunch) +- [#33103](https://github.com/apache/superset/pull/33103) fix: Allows configuration of Selenium Webdriver binary (@michael-s-molina) +- [#33109](https://github.com/apache/superset/pull/33109) fix(thumbnails): ensure consistent cache_key (@hxtmdev) +- [#32193](https://github.com/apache/superset/pull/32193) fix(dashboard): Generate screenshot via celery (@tahvane1) +- [#33087](https://github.com/apache/superset/pull/33087) fix(docker): fallback to pip if uv is not available (@hossein-khalilian) +- [#33059](https://github.com/apache/superset/pull/33059) fix: Adds missing **init** file to commands/logs (@michael-s-molina) +- [#33048](https://github.com/apache/superset/pull/33048) fix: improve error type on parse error (@justinpark) +- [#31720](https://github.com/apache/superset/pull/31720) fix(export): charts csv export in dashboards (@EmmanuelCbd) +- [#33024](https://github.com/apache/superset/pull/33024) fix(log): Missing failed query log on async queries (@justinpark) +- [#32839](https://github.com/apache/superset/pull/32839) fix: fix bug where dashboard did not enter fullscreen mode. (@LevisNgigi) +- [#28428](https://github.com/apache/superset/pull/28428) fix(dashboard): chart fullscreen issue when filter pane is collapsed (@hlvhe) +- [#29422](https://github.com/apache/superset/pull/29422) fix: `show_filters` URL parameter is not working (@hexcafe) +- [#32965](https://github.com/apache/superset/pull/32965) fix: Bar Chart (legacy) migration to keep labels layout (@michael-s-molina) +- [#30679](https://github.com/apache/superset/pull/30679) fix: fixed Add Metrics to Tree Chart (#29158) (@SBIN2010) +- [#32968](https://github.com/apache/superset/pull/32968) fix(pivot-table): Revert "fix(Pivot Table): Fix column width to respect currency config (#31414)" (@justinpark) +- [#32384](https://github.com/apache/superset/pull/32384) fix: Clicking in the body of a Markdown component does not put it into edit mode (@notHuman9504) +- [#32763](https://github.com/apache/superset/pull/32763) fix(sqllab): Invalid display of table column keys (@justinpark) +- [#32871](https://github.com/apache/superset/pull/32871) fix(Jinja): Emit time grain to table charts even if they don't have a temporal column (@Vitor-Avila) +- [#32372](https://github.com/apache/superset/pull/32372) fix(backend/async_events): allow user to configure username for Redis authentication in GLOBAL_ASYNC_QUERIES_CACHE_BACKEND (@hainenber) +- [#32873](https://github.com/apache/superset/pull/32873) fix: use role_model from security manager (@lohart13) +- [#32851](https://github.com/apache/superset/pull/32851) fix(ColorPickerControl): change color picker control width (@SBIN2010) +- [#32863](https://github.com/apache/superset/pull/32863) fix(table-chart): Do not show comparison columns config if time_compare is set to [] (@Vitor-Avila) +- [#31869](https://github.com/apache/superset/pull/31869) fix(translation): Dutch translations for Current datetime filter (@christiaan) +- [#32829](https://github.com/apache/superset/pull/32829) fix: update dataset/query catalog on DB changes (@betodealmeida) +- [#32850](https://github.com/apache/superset/pull/32850) fix(echarts): Sort series by name using natural comparison (@Vitor-Avila) +- [#32795](https://github.com/apache/superset/pull/32795) fix(log): store navigation path to get correct logging path (@justinpark) +- [#32665](https://github.com/apache/superset/pull/32665) fix: Time Comparison Feature Reverts Metric Labels to Metric Keys in Table Charts (@fardin-developer) +- [#32792](https://github.com/apache/superset/pull/32792) fix: key error in frontend on disallowed GSheets (@chrisvnimbus) +- [#32797](https://github.com/apache/superset/pull/32797) fix: CSV/Excel upload form change column dates description (@SBIN2010) +- [#32802](https://github.com/apache/superset/pull/32802) fix(sec): resolve CVE-2025-29907 and CVE-2025-25977 by pinning `jspdf` to v3 (@hainenber) +- [#32406](https://github.com/apache/superset/pull/32406) fix(model/helper): represent RLS filter clause in proper textual SQL string (@hainenber) +- [#32739](https://github.com/apache/superset/pull/32739) fix(excel export): big number truncation handling (@CharlesNkdl) +- [#32778](https://github.com/apache/superset/pull/32778) fix(config): correct slack image url in talisman (@v9dev) +- [#28350](https://github.com/apache/superset/pull/28350) fix(css): typos in styles (@Kukusik8) +- [#32775](https://github.com/apache/superset/pull/32775) fix(import): Missing catalog field in saved query schema (@Quatters) +- [#32774](https://github.com/apache/superset/pull/32774) fix(sqllab): Pass query_id as kwarg so backoff can see it (@Antonio-RiveroMartnez) +- [#32720](https://github.com/apache/superset/pull/32720) fix(chart control): Change default of "Y Axis Title Margin" (@Quatters) +- [#32761](https://github.com/apache/superset/pull/32761) fix: do not add calculated columns when syncing (@eschutho) +- [#31751](https://github.com/apache/superset/pull/31751) fix: Changing language doesn't affect echarts charts (@jpchev) +- [#28203](https://github.com/apache/superset/pull/28203) fix(contextmenu): uncaught TypeError (@sowo) +- [#32679](https://github.com/apache/superset/pull/32679) fix: ensure datasource permission in explore (@hxtmdev) +- [#32410](https://github.com/apache/superset/pull/32410) fix(import): Ensure import exceptions are logged (@withnale) +- [#32683](https://github.com/apache/superset/pull/32683) fix: coerce datetime conversion errors (@betodealmeida) +- [#32708](https://github.com/apache/superset/pull/32708) fix(logging): missing path in event data (@justinpark) +- [#32701](https://github.com/apache/superset/pull/32701) fix: boolean filters in Explore (@betodealmeida) +- [#32696](https://github.com/apache/superset/pull/32696) fix(spreadsheet uploads): make file extension comparisons case-insensitive (@sfirke) +- [#32691](https://github.com/apache/superset/pull/32691) fix(cosmetics): allow toast message to be toggled off when modal is opened (@hainenber) +- [#32699](https://github.com/apache/superset/pull/32699) fix: Signature of Celery pruner jobs (@michael-s-molina) +- [#32681](https://github.com/apache/superset/pull/32681) fix(log): Update recent_activity by event name (@justinpark) +- [#32678](https://github.com/apache/superset/pull/32678) fix: Update RELEASING/README.md (@michael-s-molina) +- [#32661](https://github.com/apache/superset/pull/32661) fix(gsheets): update params from encrypted extra (@betodealmeida) +- [#32657](https://github.com/apache/superset/pull/32657) fix(import): Import a DB connection with expanded rows enabled (@Vitor-Avila) +- [#32646](https://github.com/apache/superset/pull/32646) fix(dashboard): Ensure `dashboardId` is included in `form_data` for embedded mode (@mostopalove) +- [#32652](https://github.com/apache/superset/pull/32652) fix: Upgrade node base image to Debian 12 bookworm (@dolph) +- [#32608](https://github.com/apache/superset/pull/32608) fix(welcome): perf on distinct recent activities (@justinpark) +- [#32549](https://github.com/apache/superset/pull/32549) fix(dashboard): Support bigint value in native filters (@justinpark) +- [#32599](https://github.com/apache/superset/pull/32599) fix(Slack V2): Specify the filename for the Slack upload method (@Vitor-Avila) +- [#32572](https://github.com/apache/superset/pull/32572) fix: Log table retention policy (@michael-s-molina) +- [#32532](https://github.com/apache/superset/pull/32532) fix: add DateOffset to json serializer (@eschutho) +- [#32523](https://github.com/apache/superset/pull/32523) fix: keep calculated columns when datasource is updated (@eschutho) +- [#32507](https://github.com/apache/superset/pull/32507) fix: Show response message as default error (@eschutho) +- [#32336](https://github.com/apache/superset/pull/32336) fix(Slack): Fix Slack recipients migration to V2 (@Vitor-Avila) +- [#32511](https://github.com/apache/superset/pull/32511) fix(beat): prune_query celery task args fix (@Usiel) +- [#32499](https://github.com/apache/superset/pull/32499) fix(explore): Glitch in a tooltip with metric's name (@kgabryje) +- [#32486](https://github.com/apache/superset/pull/32486) fix: skip DB filter when doing OAuth2 (@betodealmeida) +- [#32488](https://github.com/apache/superset/pull/32488) fix(tooltip): displaying tags correctly (@rusackas) +- [#32473](https://github.com/apache/superset/pull/32473) fix(plugin-chart-echarts): remove erroneous upper bound value (@villebro) +- [#32420](https://github.com/apache/superset/pull/32420) fix(com/grid-comp/markdown): pin `remark-gfm` to v3 to allow inline code block by backticks in Markdown (@hainenber) +- [#32423](https://github.com/apache/superset/pull/32423) fix(clickhouse): get_parameters_from_uri failing when secure is true (@codenamelxl) +- [#32290](https://github.com/apache/superset/pull/32290) fix(viz): update nesting logic to handle multiple dimensions in PartitionViz (@DamianPendrak) +- [#32382](https://github.com/apache/superset/pull/32382) fix(pinot): revert join and subquery flags (@yuribogomolov) +- [#32325](https://github.com/apache/superset/pull/32325) fix: bump FAB to 4.5.4 (@dpgaspar) +- [#32344](https://github.com/apache/superset/pull/32344) fix: ensure metric_macro expands templates (@betodealmeida) +- [#32348](https://github.com/apache/superset/pull/32348) fix: clickhouse-connect engine SSH parameter (@maybedino) +- [#32362](https://github.com/apache/superset/pull/32362) fix(docker): Configure nginx for consistent port mapping and hot reloading (@vedantprajapati) +- [#32350](https://github.com/apache/superset/pull/32350) fix(firebolt): allow backslach escape for single quotes (@betodealmeida) +- [#32356](https://github.com/apache/superset/pull/32356) fix(SSHTunnelForm): make the password tooltip visible (@EnxDev) +- [#32284](https://github.com/apache/superset/pull/32284) fix(roles): Add SqlLabPermalinkRestApi as default sqlab roles. (@LevisNgigi) +- [#32035](https://github.com/apache/superset/pull/32035) fix(fe/dashboard-list): display modifier info for `Last modified` data (@hainenber) +- [#32337](https://github.com/apache/superset/pull/32337) fix: revert "fix: remove sort values on stacked totals (#31333)" (@eschutho) +- [#31993](https://github.com/apache/superset/pull/31993) fix: oauth2 trino (@aurokk) +- [#32332](https://github.com/apache/superset/pull/32332) fix: Download as PDF fails due to cache error (@kgabryje) +- [#30888](https://github.com/apache/superset/pull/30888) fix: keep the tab order (@US579) +- [#32272](https://github.com/apache/superset/pull/32272) fix(viz/table): selected column not shown in Conditional Formatting popover (@hainenber) +- [#32253](https://github.com/apache/superset/pull/32253) fix: Decimal values for Histogram bins (@michael-s-molina) +- [#32218](https://github.com/apache/superset/pull/32218) fix(Datasource): handle undefined datasource_type in fetchSyncedColumns (@tahvane1) +- [#32240](https://github.com/apache/superset/pull/32240) fix: upgrade to 3.11.11-slim-bookworm to address critical vulnerabilities (@gpchandran) +- [#31333](https://github.com/apache/superset/pull/31333) fix: remove sort values on stacked totals (@eschutho) +- [#32227](https://github.com/apache/superset/pull/32227) fix: Update 'Last modified' time when modifying RLS rules (@fardin-developer) +- [#32115](https://github.com/apache/superset/pull/32115) fix(Scope): Correct issue where filters appear out of scope when sort is unchecked. (@LevisNgigi) +- [#32224](https://github.com/apache/superset/pull/32224) fix(sqllab): close the table tab (@justinpark) +- [#32212](https://github.com/apache/superset/pull/32212) fix: set `Rich tooltip` -> 'Show percentage' to false by default (@mistercrunch) +- [#32222](https://github.com/apache/superset/pull/32222) fix(SaveDatasetModal): repairs field alignment in the SaveDatasetModal component (@EnxDev) +- [#32211](https://github.com/apache/superset/pull/32211) fix: hydrate datasetsStatus (@betodealmeida) +- [#32195](https://github.com/apache/superset/pull/32195) fix: handlebars html and css templates reset on dataset update (@DamianPendrak) +- [#32176](https://github.com/apache/superset/pull/32176) fix: TDengine move tdengine.png to databases/ subfolder (@DuanKuanJun) +- [#32185](https://github.com/apache/superset/pull/32185) fix: Adds an entry to UPDATING.md about DISABLE_LEGACY_DATASOURCE_EDITOR (@michael-s-molina) +- [#32154](https://github.com/apache/superset/pull/32154) fix(sqllab): correct URL format for SQL Lab permalinks (@LevisNgigi) +- [#30903](https://github.com/apache/superset/pull/30903) fix(virtual dataset sync): Sync virtual dataset columns when changing the SQL query (@fisjac) +- [#32163](https://github.com/apache/superset/pull/32163) fix(docker): Docker python-translation-build (@EmmanuelCbd) +- [#32156](https://github.com/apache/superset/pull/32156) fix: ScreenshotCachePayload serialization (@betodealmeida) +- [#32151](https://github.com/apache/superset/pull/32151) fix(releasing): fix borked SVN-based image building process (@hainenber) +- [#32137](https://github.com/apache/superset/pull/32137) fix: copy oauth2 capture to `get_sqla_engine` (@betodealmeida) +- [#32135](https://github.com/apache/superset/pull/32135) fix: Local tarball Docker container is missing zstd dependency (@michael-s-molina) +- [#32133](https://github.com/apache/superset/pull/32133) fix: No virtual environment when running Docker translation compiler (@michael-s-molina) +- [#32040](https://github.com/apache/superset/pull/32040) fix(ci): ephemeral env, handle different label, create comment (@dpgaspar) +- [#32064](https://github.com/apache/superset/pull/32064) fix(datepicker): Full width datepicker on filter value select (@msyavuz) +- [#32122](https://github.com/apache/superset/pull/32122) fix: Histogram examples config (@michael-s-molina) +- [#32053](https://github.com/apache/superset/pull/32053) fix: enforce `ALERT_REPORTS_MAX_CUSTOM_SCREENSHOT_WIDTH` (@betodealmeida) +- [#31757](https://github.com/apache/superset/pull/31757) fix(thumbnail cache): Enabling force parameter on screenshot/thumbnail cache (@fisjac) +- [#32061](https://github.com/apache/superset/pull/32061) fix(DatePicker): Increase z-index over Modal (@geido) +- [#32031](https://github.com/apache/superset/pull/32031) fix(fe/explore): prevent runtime error when editing Dataset-origin Chart with empty title (@hainenber) +- [#32045](https://github.com/apache/superset/pull/32045) fix: Revert "fix: re-enable cypress checks" (@mistercrunch) +- [#32008](https://github.com/apache/superset/pull/32008) fix: re-enable cypress checks (@mistercrunch) +- [#32017](https://github.com/apache/superset/pull/32017) fix: eph env + improve docker images to run in userspace (@mistercrunch) +- [#31340](https://github.com/apache/superset/pull/31340) fix(ci): change ephemeral env to use github labels instead of comments (@dpgaspar) +- [#32025](https://github.com/apache/superset/pull/32025) fix: Filters badge disappeared (@kgabryje) +- [#32015](https://github.com/apache/superset/pull/32015) fix(issue #31927): TimeGrain.WEEK_STARTING_MONDAY (@AdrianMastronardi) +- [#30716](https://github.com/apache/superset/pull/30716) fix: Reordering echart props to fix confidence interval in Mixed Charts (@geotab-data-platform) +- [#32005](https://github.com/apache/superset/pull/32005) fix(sqllab): tab layout truncated (@justinpark) +- [#29417](https://github.com/apache/superset/pull/29417) fix(verbose map): Correct raw metrics handling in verbose map (@mcdogg17) +- [#31962](https://github.com/apache/superset/pull/31962) fix: proper URL building (@betodealmeida) +- [#31941](https://github.com/apache/superset/pull/31941) fix(timezoneselector): Correct the order to match names first (@msyavuz) +- [#25166](https://github.com/apache/superset/pull/25166) fix: correct value for config variable `UPLOAD_FOLDER` (@sebastianliebscher) +- [#31948](https://github.com/apache/superset/pull/31948) fix: Load cached DB metadata as DatasourceName and add catalog to schema_list cache key (@Vitor-Avila) +- [#31809](https://github.com/apache/superset/pull/31809) fix: Prevent undo functionality from referencing incorrect dashboard edits (@fardin-developer) +- [#30949](https://github.com/apache/superset/pull/30949) fix: adjust line type as well as weight for time series (@eschutho) +- [#31933](https://github.com/apache/superset/pull/31933) fix(E2E): Fix flaky Dashboard list delete test (@geido) +- [#31867](https://github.com/apache/superset/pull/31867) fix(date_parser): fixed bug for advanced time range filter (@alexandrusoare) +- [#31873](https://github.com/apache/superset/pull/31873) fix(documentation): updated link to CORS_OPTIONS in Networking Settings (@ankur-zignite91) +- [#31910](https://github.com/apache/superset/pull/31910) fix: add catalog to cache key when getting tables/views (@betodealmeida) +- [#31837](https://github.com/apache/superset/pull/31837) fix(bigquery): return no catalogs when creds not set (@betodealmeida) +- [#31848](https://github.com/apache/superset/pull/31848) fix: d3.count doesn't exist (@mistercrunch) +- [#31830](https://github.com/apache/superset/pull/31830) fix: fix/suppress webpack console warnings (@mistercrunch) +- [#31834](https://github.com/apache/superset/pull/31834) fix(OAuth): Remove masked_encrypted_extra from DB update properties (@Vitor-Avila) +- [#31798](https://github.com/apache/superset/pull/31798) fix(Embedded): Skip CSRF validation for dashboard download endpoints (@Vitor-Avila) +- [#31815](https://github.com/apache/superset/pull/31815) fix(modal): fixed z-index issue (@alexandrusoare) +- [#31774](https://github.com/apache/superset/pull/31774) fix: corrects spelling of USE_ANALAGOUS_COLORS to be USE_ANALOGOUS_COLORS (@rusackas) +- [#31777](https://github.com/apache/superset/pull/31777) fix(oauth): Handle updates to the OAuth config (@Vitor-Avila) +- [#31789](https://github.com/apache/superset/pull/31789) fix(button): change back button styles for dropdown buttons (@msyavuz) +- [#31752](https://github.com/apache/superset/pull/31752) fix: Heatmap sorting (@michael-s-molina) +- [#31742](https://github.com/apache/superset/pull/31742) fix: GHA frontend builds fail when frontends hasn't changed (@mistercrunch) +- [#31732](https://github.com/apache/superset/pull/31732) fix: docker builds in forks (@mistercrunch) +- [#31606](https://github.com/apache/superset/pull/31606) fix: docker-compose-image-tag fails to start (@mistercrunch) +- [#31710](https://github.com/apache/superset/pull/31710) fix(inthewild): Update companies using superset (@gwthm-in) +- [#31673](https://github.com/apache/superset/pull/31673) fix: typo in plugin-chart-echats controls (@vhf) +- [#31688](https://github.com/apache/superset/pull/31688) fix(helm): change values.yaml comments (@sule26) +- [#31588](https://github.com/apache/superset/pull/31588) fix: install uv in docker-bootstrap (@mistercrunch) +- [#31583](https://github.com/apache/superset/pull/31583) fix(docs): get quickstart guide working again (@sfirke) +- [#31561](https://github.com/apache/superset/pull/31561) fix: add various recent issues on master CI (@mistercrunch) +- [#31493](https://github.com/apache/superset/pull/31493) fix: master docker builds fail because of multi-platform builds can't --load (@mistercrunch) +- [#31483](https://github.com/apache/superset/pull/31483) fix: Card component background color (@kgabryje) +- [#31472](https://github.com/apache/superset/pull/31472) fix: Tooltip covers the date selector in native filters (@kgabryje) +- [#31473](https://github.com/apache/superset/pull/31473) fix(explore): Styling issue in Search Metrics input field (@kgabryje) +- [#31449](https://github.com/apache/superset/pull/31449) fix(filter options): full size list item targets (@rusackas) +- [#31458](https://github.com/apache/superset/pull/31458) fix(api): typo api.py (@zero-stroke) +- [#31385](https://github.com/apache/superset/pull/31385) fix: docker refactor (@mistercrunch) +- [#31374](https://github.com/apache/superset/pull/31374) fix(Dashboard): Sync color configuration via dedicated endpoint (@geido) +- [#31411](https://github.com/apache/superset/pull/31411) fix: pkg_resources is getting deprecated (@mistercrunch) +- [#31391](https://github.com/apache/superset/pull/31391) fix: don't include chromium on ephemeral envs (@mistercrunch) +- [#31387](https://github.com/apache/superset/pull/31387) fix: Revert "chore(deps-dev): bump esbuild from 0.20.0 to 0.24.0 in /super… (@sadpandajoe) +- [#31236](https://github.com/apache/superset/pull/31236) fix: ephemeral envs fail on noop (@dpgaspar) +- [#31350](https://github.com/apache/superset/pull/31350) fix(alerts&reports): tabs with userfriendly urls (@tahvane1) +- [#30956](https://github.com/apache/superset/pull/30956) fix: added missing pod labels for init job (@glothriel) +- [#31279](https://github.com/apache/superset/pull/31279) fix(filters): improving the add filter/divider UI. (@rusackas) +- [#31155](https://github.com/apache/superset/pull/31155) fix: helm chart deploy to open PRs to now-protected gh-pages branch (@mistercrunch) +- [#31152](https://github.com/apache/superset/pull/31152) fix: try to re-enable gh-pages (@mistercrunch) +- [#31148](https://github.com/apache/superset/pull/31148) fix: touch helm/ folder to trigger doc deploy in CI (@mistercrunch) +- [#31035](https://github.com/apache/superset/pull/31035) fix: ephemeral environments missing env var (@mistercrunch) +- [#30966](https://github.com/apache/superset/pull/30966) fix(helm-chart): Fix broken PodDisruptionBudget due to introduction of extraLabels. (@theoriginalgri) +- [#30964](https://github.com/apache/superset/pull/30964) fix(Card): Use correct class names for Ant Design 5 Card component (@geido) +- [#30924](https://github.com/apache/superset/pull/30924) fix(helm): use submodule on helm release action (@villebro) +- [#30767](https://github.com/apache/superset/pull/30767) fix(empty dashboards): Allow downloading a screenshot of an empty dashboard (@msyavuz) +- [#30885](https://github.com/apache/superset/pull/30885) fix(docs): add missing bracket in openID config (@samarsrivastav) +- [#30858](https://github.com/apache/superset/pull/30858) fix(chart data): removing query from /chart/data payload when accessing as guest user (@fisjac) +- [#30848](https://github.com/apache/superset/pull/30848) fix(time_comparison): Allow deleting dates when using custom shift (@Antonio-RiveroMartnez) +- [#28524](https://github.com/apache/superset/pull/28524) fix: warning emits an error (@eschutho) +- [#30682](https://github.com/apache/superset/pull/30682) fix(explore): Update tooltip copy for rendering html in tables and pivot tables (@yousoph) +- [#30618](https://github.com/apache/superset/pull/30618) fix(mssql db_engine_spec): adds uniqueidentifier to column_type_mappings (@rparsonsbb) +- [#27142](https://github.com/apache/superset/pull/27142) fix(chart): apply number format in Box Plot tooltip only where necessary (@goto-loop) +- [#30608](https://github.com/apache/superset/pull/30608) fix(country-map): Rename incorrect Vietnam province name for Country Map (@tienhung2812) +- [#30702](https://github.com/apache/superset/pull/30702) fix(Dashboard): DatePicker to not autoclose modal (@geido) +- [#30688](https://github.com/apache/superset/pull/30688) fix: bump FAB to 4.5.2 (@dpgaspar) +- [#30659](https://github.com/apache/superset/pull/30659) fix: Link Checking (@CodeWithEmad) +- [#30661](https://github.com/apache/superset/pull/30661) fix: Domain 'undefined' error in Storybook (@kgabryje) +- [#30626](https://github.com/apache/superset/pull/30626) fix: Module is not defined in Partition chart (@michael-s-molina) +- [#30616](https://github.com/apache/superset/pull/30616) fix(docs): leading whitespace line is causing page title and header to be malformed (@sfirke) +- [#30606](https://github.com/apache/superset/pull/30606) fix: Set correct amount of steps to avoid confusing logs while loading examples (@deathstrokedarksky) +- [#30522](https://github.com/apache/superset/pull/30522) fix(SQL Lab): hang when result set size is too big (@anamitraadhikari) +- [#30443](https://github.com/apache/superset/pull/30443) fix(Jinja metric macro): Support Drill By and Excel/CSV download without a dataset ID (@Vitor-Avila) +- [#30569](https://github.com/apache/superset/pull/30569) fix(dev-server): Revert "chore(fe): bump webpack-related packages to v5" (@geido) +- [#30069](https://github.com/apache/superset/pull/30069) fix(frontend/generator): fix failed Viz plugin build due to missing JSDOM config and dep (@hainenber) +- [#30277](https://github.com/apache/superset/pull/30277) fix(examples): fix examples uri for sqlite (@villebro) +- [#30509](https://github.com/apache/superset/pull/30509) fix(plugin/echarts): correct enum values for LABEL_POSITION map (@hainenber) +- [#30500](https://github.com/apache/superset/pull/30500) fix(sqllab): Remove redundant scrolling (@justinpark) +- [#30349](https://github.com/apache/superset/pull/30349) fix(radar-chart): metric options not available & add `min` option (@goncaloacteixeira) +- [#30493](https://github.com/apache/superset/pull/30493) fix(Package.json): Bump dayjs version (@geido) +- [#30406](https://github.com/apache/superset/pull/30406) fix(language): pt_BR translation (@diegolnasc) +- [#30441](https://github.com/apache/superset/pull/30441) fix: battling cypress' dashboard feature (@mistercrunch) +- [#30430](https://github.com/apache/superset/pull/30430) fix: cypress on master doesn't work because of --parallel flag (@mistercrunch) +- [#29444](https://github.com/apache/superset/pull/29444) fix(plugin/country/map): rectify naming for some Vietnamese provinces (@hainenber) +- [#30388](https://github.com/apache/superset/pull/30388) fix(ECharts): Revert ECharts version bump (@geido) +- [#30340](https://github.com/apache/superset/pull/30340) fix(CI): increase node JS heap size (@rusackas) +- [#30325](https://github.com/apache/superset/pull/30325) fix(db_engine_specs): add a few missing time grains to Postgres spec (@sfirke) +- [#30273](https://github.com/apache/superset/pull/30273) fix(dashboard): invalid button style in undo/redo button (@justinpark) +- [#30099](https://github.com/apache/superset/pull/30099) fix: Move copying translation files before npm run build in Docker (@martyngigg) +- [#30279](https://github.com/apache/superset/pull/30279) fix(install/docker): use zstd-baked image for building superset-frontend in containerized env (@hainenber) +- [#30234](https://github.com/apache/superset/pull/30234) fix(deps): release new embedded sdk (@rusackas) +- [#30237](https://github.com/apache/superset/pull/30237) fix(docs): change flask-oidc url (@drblack666) +- [#30217](https://github.com/apache/superset/pull/30217) fix(sdk): use latest @supserset-ui/switchboard version to avoid pulling empty dependency (@hainenber) +- [#30147](https://github.com/apache/superset/pull/30147) fix(docs): typo in docker-compose.mdx (@alexengrig) +- [#30148](https://github.com/apache/superset/pull/30148) fix: Adds the Deprecated label to Time-series Percent Change chart (@michael-s-molina) +- [#30141](https://github.com/apache/superset/pull/30141) fix(sqllab): race condition when updating same cursor position (@justinpark) +- [#30041](https://github.com/apache/superset/pull/30041) fix: Revert "fix(list/chart views): Chart Properties modal now has transitions" (@rusackas) +- [#30034](https://github.com/apache/superset/pull/30034) fix: Handle zstd encoding in webpack proxy config (@kgabryje) +- [#29916](https://github.com/apache/superset/pull/29916) fix: duplicate `truncateXAxis` option in `BarChart` (@dmitriyVasilievich1986) +- [#30013](https://github.com/apache/superset/pull/30013) fix(translations): Fixed APPLY translation in Spanish (@jvines) +- [#30001](https://github.com/apache/superset/pull/30001) fix: Reports are not sent when selecting to send as PNG, CSV or text (@eschutho) +- [#29686](https://github.com/apache/superset/pull/29686) fix: Removed fixed width constraint from Save button (@goldjee) +- [#29951](https://github.com/apache/superset/pull/29951) fix(i18n): translation fix in server side generated time grains (@Seboeb) +- [#29938](https://github.com/apache/superset/pull/29938) fix: thumbnail url json response was malformed (@eschutho) +- [#29944](https://github.com/apache/superset/pull/29944) fix: only show dataset name in list (@eschutho) +- [#29935](https://github.com/apache/superset/pull/29935) fix: Fix delete_fake_db (@stamplevskiyd) +- [#29522](https://github.com/apache/superset/pull/29522) fix(cli): add impersonate_user to db import (@chessman) +- [#29895](https://github.com/apache/superset/pull/29895) fix(PivotTable): Pass string only to safeHtmlSpan (@geido) +- [#29864](https://github.com/apache/superset/pull/29864) fix: mypy issue on py3.9 + prevent similar issues (@mistercrunch) +- [#29861](https://github.com/apache/superset/pull/29861) fix: mypy fails related to simplejson.dumps (@mistercrunch) +- [#24411](https://github.com/apache/superset/pull/24411) fix(docs): update timescale.png (@mathisve) +- [#29851](https://github.com/apache/superset/pull/29851) fix: Add missing icons (@kgabryje) +- [#29591](https://github.com/apache/superset/pull/29591) fix: machine auth for GAQ enabled deployments (@harshit2283) +- [#29798](https://github.com/apache/superset/pull/29798) fix: set default timezone to UTC for cron timezone conversions (@danielli-ziprecruiter) +- [#28796](https://github.com/apache/superset/pull/28796) fix(list/chart views): Chart Properties modal now has transitions (@rusackas) +- [#29688](https://github.com/apache/superset/pull/29688) fix(ci): release process for labeling PRs (@mistercrunch) +- [#29779](https://github.com/apache/superset/pull/29779) fix: remove --no-optional from docker-compose build (@mistercrunch) + +**Others** + +- [#33745](https://github.com/apache/superset/pull/33745) build: update Dockerfile to 3.11.13-slim-bookworm (@gpchandran) +- [#33612](https://github.com/apache/superset/pull/33612) chore: update Dockerfile - Upgrade to 3.11.12 (@gpchandran) +- [#33339](https://github.com/apache/superset/pull/33339) chore(🦾): bump python h11 0.14.0 -> 0.16.0 (@github-actions[bot]) +- [#32745](https://github.com/apache/superset/pull/32745) chore(🦾): bump python sqlglot 26.1.3 -> 26.11.1 (@github-actions[bot]) +- [#32239](https://github.com/apache/superset/pull/32239) docs: adding notes about using uv instead of raw pip (@mistercrunch) +- [#32221](https://github.com/apache/superset/pull/32221) chore(ci): fix ephemeral env null issue number (v2) (@dpgaspar) +- [#32220](https://github.com/apache/superset/pull/32220) chore(ci): fix ephemeral env null issue number (@dpgaspar) +- [#32030](https://github.com/apache/superset/pull/32030) chore(timeseries charts): adjust legend width by padding (@eschutho) +- [#32062](https://github.com/apache/superset/pull/32062) chore: Re-enable asnyc event API tests (@Vitor-Avila) +- [#32004](https://github.com/apache/superset/pull/32004) refactor(Radio): Upgrade Radio Component to Ant Design 5 (@EnxDev) +- [#32054](https://github.com/apache/superset/pull/32054) chore: Add more database-related tests (follow up to #31948) (@Vitor-Avila) +- [#31811](https://github.com/apache/superset/pull/31811) chore(Network Errors): Update network errors on filter bars and charts (@msyavuz) +- [#31794](https://github.com/apache/superset/pull/31794) chore: Removing DASHBOARD_CROSS_FILTERS flag and all that comes with it. (@rusackas) +- [#32013](https://github.com/apache/superset/pull/32013) chore: add UPDATING note for CSV_UPLOAD_MAX_SIZE removal (@dpgaspar) +- [#31961](https://github.com/apache/superset/pull/31961) refactor: Upgrade to React 17 (@kgabryje) +- [#32007](https://github.com/apache/superset/pull/32007) chore(fe): correct typing for sheetsColumnNames (@hainenber) +- [#32000](https://github.com/apache/superset/pull/32000) refactor: Remove CSV upload size limit and related validation (@sha174n) +- [#31421](https://github.com/apache/superset/pull/31421) refactor(Shared_url_query): Fix shared query URL access for SQL Lab users. (@LevisNgigi) +- [#31980](https://github.com/apache/superset/pull/31980) chore: Add FYND to INTHEWILD.md (@darpanjain07) +- [#31976](https://github.com/apache/superset/pull/31976) refactor: Removes the legacy dataset editor (@michael-s-molina) +- [#31858](https://github.com/apache/superset/pull/31858) chore: refactor Alert-related components (@mistercrunch) +- [#31547](https://github.com/apache/superset/pull/31547) chore(deps): bump react-transition-group and @types/react-transition-group in /superset-frontend (@dependabot[bot]) +- [#31963](https://github.com/apache/superset/pull/31963) chore(build): enforce eslint rule banning antd imports outside of core Superset components (@rusackas) +- [#31965](https://github.com/apache/superset/pull/31965) chore: fix `tsc` errors (@hainenber) +- [#31860](https://github.com/apache/superset/pull/31860) chore: Empty state refactor (@mistercrunch) +- [#31844](https://github.com/apache/superset/pull/31844) chore: replace selenium user with fixed user (@villebro) +- [#31943](https://github.com/apache/superset/pull/31943) refactor: Removes legacy dashboard endpoints (@michael-s-molina) +- [#31942](https://github.com/apache/superset/pull/31942) refactor: Removes legacy CSS template endpoint (@michael-s-molina) +- [#31819](https://github.com/apache/superset/pull/31819) chore(fe): migrate 6 Enzyme-based unit tests to RTL (@hainenber) +- [#31947](https://github.com/apache/superset/pull/31947) chore: bump FAB to 4.5.3 (@dpgaspar) +- [#30284](https://github.com/apache/superset/pull/30284) chore(GAQ): Remove GLOBAL_ASYNC_QUERIES_REDIS_CONFIG (@nsivarajan) +- [#31926](https://github.com/apache/superset/pull/31926) chore: cypress set up tweaks (@mistercrunch) +- [#31905](https://github.com/apache/superset/pull/31905) chore: Reduces the form_data_key length (@michael-s-molina) +- [#31460](https://github.com/apache/superset/pull/31460) docs: Removed mentioning of .env-non-dev in docker/README.md (@nikelborm) +- [#31907](https://github.com/apache/superset/pull/31907) chore: replace Lodash usage with native JS implementation (@hainenber) +- [#31699](https://github.com/apache/superset/pull/31699) refactor(Menu): Upgrade Menu Component to Ant Design 5 (@geido) +- [#31908](https://github.com/apache/superset/pull/31908) chore(fe): dev deps cleanup (@hainenber) +- [#31916](https://github.com/apache/superset/pull/31916) docs: clarify port configuration for Cypress (@mistercrunch) +- [#29163](https://github.com/apache/superset/pull/29163) refactor(sqllab): migrate share queries via kv by permalink (@justinpark) +- [#29121](https://github.com/apache/superset/pull/29121) perf(dashboard): dashboard list endpoint returning large and unnecessary data (@Always-prog) +- [#31894](https://github.com/apache/superset/pull/31894) chore(config): Deprecating Domain Sharding (@rusackas) +- [#31795](https://github.com/apache/superset/pull/31795) chore: Re-enable skipped tests (@michael-s-molina) +- [#31875](https://github.com/apache/superset/pull/31875) chore: add a disable for pylint (@betodealmeida) +- [#31874](https://github.com/apache/superset/pull/31874) docs: add a note about accessing the dev env's postgres database (@mistercrunch) +- [#31845](https://github.com/apache/superset/pull/31845) chore: add eslint to pre-commit hooks (@mistercrunch) +- [#31847](https://github.com/apache/superset/pull/31847) chore(ci): auto delete branches on merge (@rusackas) +- [#31846](https://github.com/apache/superset/pull/31846) chore: properly import expect from chai in cypress-base/cypress/support/e2e.ts (@mistercrunch) +- [#31831](https://github.com/apache/superset/pull/31831) chore: bump @ant-design/icons to fix fill-rule console warning (@mistercrunch) +- [#31503](https://github.com/apache/superset/pull/31503) chore: python version to 3.11 (while supporting 3.10) (@mistercrunch) +- [#31761](https://github.com/apache/superset/pull/31761) build(eslint): disabling wildcard imports with eslint (@rusackas) +- [#25933](https://github.com/apache/superset/pull/25933) chore(deps): bump selenium 4.14.0+ (@gnought) +- [#31820](https://github.com/apache/superset/pull/31820) chore(tests): Changing the logic for an intermittent tag test (@Vitor-Avila) +- [#31631](https://github.com/apache/superset/pull/31631) refactor(bulk_select): Fix bulk select tagging issues for users (@LevisNgigi) +- [#31019](https://github.com/apache/superset/pull/31019) refactor(date picker): Migrate Date Picker to Ant Design 5 (@msyavuz) +- [#31787](https://github.com/apache/superset/pull/31787) docs: improve dev python environment install (@sha174n) +- [#31797](https://github.com/apache/superset/pull/31797) chore: adding Antonio as a helm codeowner (@eschutho) +- [#31452](https://github.com/apache/superset/pull/31452) refactor(dashboard): Migrate ResizableContainer to TypeScript and functional component (@EnxDev) +- [#31791](https://github.com/apache/superset/pull/31791) chore: Skips integration tests affected by legacy charts removal (@michael-s-molina) +- [#31661](https://github.com/apache/superset/pull/31661) build(deps-dev): bump css-loader from 6.8.1 to 7.1.2 in /superset-frontend (@dependabot[bot]) +- [#31668](https://github.com/apache/superset/pull/31668) build(deps-dev): bump css-minimizer-webpack-plugin from 5.0.1 to 7.0.0 in /superset-frontend (@dependabot[bot]) +- [#31754](https://github.com/apache/superset/pull/31754) refactor: Removes Apply to all panels filters scope configuration (@michael-s-molina) +- [#31623](https://github.com/apache/superset/pull/31623) refactor(Button): Upgrade Button component to Antd5 (@alexandrusoare) +- [#31756](https://github.com/apache/superset/pull/31756) docs: add Remita to list (@mujibishola) +- [#31750](https://github.com/apache/superset/pull/31750) docs: add cover genius to the user list (@US579) +- [#31412](https://github.com/apache/superset/pull/31412) chore(ff): deprecating `DRILL_TO_DETAIL` feature flag to launch it prime-time (@rusackas) +- [#31718](https://github.com/apache/superset/pull/31718) refactor(Steps): Migrate Steps to Ant Design 5 (@msyavuz) +- [#31537](https://github.com/apache/superset/pull/31537) chore(deps): bump react-virtualized-auto-sizer from 1.0.24 to 1.0.25 in /superset-frontend (@dependabot[bot]) +- [#31552](https://github.com/apache/superset/pull/31552) chore(deps-dev): bump eslint-plugin-react-hooks from 4.6.0 to 4.6.2 in /superset-frontend (@dependabot[bot]) +- [#31545](https://github.com/apache/superset/pull/31545) chore(deps-dev): bump webpack from 5.94.0 to 5.97.1 in /superset-frontend (@dependabot[bot]) +- [#31551](https://github.com/apache/superset/pull/31551) chore(deps-dev): bump eslint-plugin-cypress from 3.5.0 to 3.6.0 in /superset-frontend (@dependabot[bot]) +- [#31559](https://github.com/apache/superset/pull/31559) chore(deps): bump abortcontroller-polyfill from 1.7.5 to 1.7.8 in /superset-frontend (@dependabot[bot]) +- [#31653](https://github.com/apache/superset/pull/31653) build(deps): update @emotion/cache requirement from ^11.4.0 to ^11.14.0 in /superset-frontend/packages/superset-ui-demo (@dependabot[bot]) +- [#31664](https://github.com/apache/superset/pull/31664) build(deps): bump markdown-to-jsx from 7.4.7 to 7.7.2 in /superset-frontend (@dependabot[bot]) +- [#31665](https://github.com/apache/superset/pull/31665) build(deps): bump html-webpack-plugin from 5.6.0 to 5.6.3 in /superset-frontend (@dependabot[bot]) +- [#31666](https://github.com/apache/superset/pull/31666) build(deps-dev): bump @emotion/babel-plugin from 11.12.0 to 11.13.5 in /superset-frontend (@dependabot[bot]) +- [#31667](https://github.com/apache/superset/pull/31667) build(deps-dev): bump jsdom from 24.1.1 to 25.0.1 in /superset-frontend (@dependabot[bot]) +- [#31685](https://github.com/apache/superset/pull/31685) build(deps): bump jinja2 from 3.1.4 to 3.1.5 in /superset/translations (@dependabot[bot]) +- [#31622](https://github.com/apache/superset/pull/31622) chore: replace `imp` built-in module usage for future Python3.12 usage (@hainenber) +- [#31712](https://github.com/apache/superset/pull/31712) chore(fe/sec): resolve High CVE-2024-21538 and Moderate CVE-2024-55565 by bumping `nanoid` and `cross-spawn` (@hainenber) +- [#31627](https://github.com/apache/superset/pull/31627) chore(helm): bump helm on CI to latest version (@villebro) +- [#31701](https://github.com/apache/superset/pull/31701) chore: add helm code owners (@villebro) +- [#31691](https://github.com/apache/superset/pull/31691) docs: add Open edX to users list (@pomegranited) +- [#31693](https://github.com/apache/superset/pull/31693) refactor(space): Migrate Space to Ant Design 5 (@msyavuz) +- [#31530](https://github.com/apache/superset/pull/31530) chore(deps-dev): bump eslint from 9.14.0 to 9.17.0 in /superset-websocket (@dependabot[bot]) +- [#31670](https://github.com/apache/superset/pull/31670) build(deps): update echarts requirement from ^5.4.1 to ^5.6.0 in /superset-frontend/plugins/plugin-chart-echarts (@dependabot[bot]) +- [#31652](https://github.com/apache/superset/pull/31652) build(deps): update chalk requirement from ^5.4.0 to ^5.4.1 in /superset-frontend/packages/generator-superset (@dependabot[bot]) +- [#31655](https://github.com/apache/superset/pull/31655) build(deps): bump core-js from 3.38.1 to 3.39.0 in /superset-frontend/packages/superset-ui-demo (@dependabot[bot]) +- [#31656](https://github.com/apache/superset/pull/31656) build(deps): bump antd from 5.22.5 to 5.22.7 in /docs (@dependabot[bot]) +- [#31657](https://github.com/apache/superset/pull/31657) build(deps-dev): update @babel/core requirement from ^7.23.9 to ^7.26.0 in /superset-frontend/packages/superset-ui-demo (@dependabot[bot]) +- [#31658](https://github.com/apache/superset/pull/31658) build(deps): update @emotion/react requirement from ^11.13.3 to ^11.14.0 in /superset-frontend/packages/superset-ui-demo (@dependabot[bot]) +- [#31662](https://github.com/apache/superset/pull/31662) build(deps-dev): bump @types/node from 22.7.4 to 22.10.3 in /superset-websocket (@dependabot[bot]) +- [#31663](https://github.com/apache/superset/pull/31663) build(deps-dev): bump typescript-eslint from 8.12.2 to 8.19.0 in /superset-websocket (@dependabot[bot]) +- [#31672](https://github.com/apache/superset/pull/31672) build(deps-dev): update @types/node requirement from ^22.5.4 to ^22.10.3 in /superset-frontend/packages/superset-ui-core (@dependabot[bot]) +- [#31633](https://github.com/apache/superset/pull/31633) refactor(empty): Migrate Empty component to Ant Design 5 (@msyavuz) +- [#31607](https://github.com/apache/superset/pull/31607) refactor(Divider): Migrate Divider to Ant Design 5 (@msyavuz) +- [#31310](https://github.com/apache/superset/pull/31310) refactor(moment): Replace Moment.js with DayJs (@msyavuz) +- [#30778](https://github.com/apache/superset/pull/30778) build(deps-dev): update @types/jest requirement from ^29.5.12 to ^29.5.14 in /superset-frontend/plugins/plugin-chart-handlebars (@dependabot[bot]) +- [#31526](https://github.com/apache/superset/pull/31526) chore(deps): bump hot-shots from 10.0.0 to 10.2.1 in /superset-websocket (@dependabot[bot]) +- [#31538](https://github.com/apache/superset/pull/31538) chore(deps-dev): update @babel/preset-react requirement from ^7.23.3 to ^7.26.3 in /superset-frontend/packages/superset-ui-demo (@dependabot[bot]) +- [#31217](https://github.com/apache/superset/pull/31217) chore(deps-dev): bump eslint-plugin-jest-dom from 3.6.5 to 5.5.0 in /superset-frontend (@dependabot[bot]) +- [#31541](https://github.com/apache/superset/pull/31541) chore(deps): bump antd from 5.22.2 to 5.22.5 in /docs (@dependabot[bot]) +- [#31536](https://github.com/apache/superset/pull/31536) chore(deps): bump prism-react-renderer from 2.4.0 to 2.4.1 in /docs (@dependabot[bot]) +- [#30322](https://github.com/apache/superset/pull/30322) build(deps): bump find-my-way and @applitools/eyes-cypress in /superset-frontend/cypress-base (@dependabot[bot]) +- [#30789](https://github.com/apache/superset/pull/30789) build(deps-dev): update @types/lodash requirement from ^4.17.7 to ^4.17.13 in /superset-frontend/packages/superset-ui-core (@dependabot[bot]) +- [#31523](https://github.com/apache/superset/pull/31523) chore(deps-dev): bump @types/lodash from 4.17.7 to 4.17.13 in /superset-websocket (@dependabot[bot]) +- [#31546](https://github.com/apache/superset/pull/31546) chore(deps-dev): bump @types/rison from 0.0.9 to 0.1.0 in /superset-frontend (@dependabot[bot]) +- [#31557](https://github.com/apache/superset/pull/31557) chore(deps): bump react-reverse-portal from 2.1.1 to 2.1.2 in /superset-frontend (@dependabot[bot]) +- [#31577](https://github.com/apache/superset/pull/31577) docs: add Virtuoso QA to users list (@shubham-rohatgi) +- [#31520](https://github.com/apache/superset/pull/31520) chore(deps): bump debug from 4.3.7 to 4.4.0 in /superset-websocket/utils/client-ws-app (@dependabot[bot]) +- [#30474](https://github.com/apache/superset/pull/30474) build(deps-dev): bump thread-loader from 4.0.2 to 4.0.4 in /superset-frontend (@dependabot[bot]) +- [#30085](https://github.com/apache/superset/pull/30085) build(deps): bump gh-pages from 5.0.0 to 6.1.1 in /superset-frontend/packages/superset-ui-demo (@dependabot[bot]) +- [#31558](https://github.com/apache/superset/pull/31558) chore(deps-dev): bump eslint-import-resolver-typescript from 3.6.3 to 3.7.0 in /superset-frontend (@dependabot[bot]) +- [#31521](https://github.com/apache/superset/pull/31521) chore(deps-dev): bump prettier from 3.3.3 to 3.4.2 in /superset-websocket (@dependabot[bot]) +- [#30785](https://github.com/apache/superset/pull/30785) build(deps-dev): update @types/underscore requirement from ^1.11.15 to ^1.13.0 in /superset-frontend/plugins/legacy-preset-chart-deckgl (@dependabot[bot]) +- [#30779](https://github.com/apache/superset/pull/30779) build(deps-dev): update @types/lodash requirement from ^4.17.7 to ^4.17.13 in /superset-frontend/plugins/plugin-chart-handlebars (@dependabot[bot]) +- [#31539](https://github.com/apache/superset/pull/31539) chore(deps-dev): bump webpack from 5.96.1 to 5.97.1 in /docs (@dependabot[bot]) +- [#31540](https://github.com/apache/superset/pull/31540) chore(deps): bump @algolia/client-search from 5.15.0 to 5.18.0 in /docs (@dependabot[bot]) +- [#27809](https://github.com/apache/superset/pull/27809) build(deps): bump @math.gl/web-mercator from 3.6.3 to 4.0.1 in /superset-frontend/plugins/legacy-preset-chart-deckgl (@dependabot[bot]) +- [#31529](https://github.com/apache/superset/pull/31529) chore(deps): update @deck.gl/aggregation-layers requirement from ^9.0.37 to ^9.0.38 in /superset-frontend/plugins/legacy-preset-chart-deckgl (@dependabot[bot]) +- [#31572](https://github.com/apache/superset/pull/31572) chore(deps): bump gh-pages from 5.0.0 to 6.2.0 in /superset-frontend/packages/superset-ui-demo (@dependabot[bot]) +- [#30458](https://github.com/apache/superset/pull/30458) build(deps): bump @types/d3-format from 1.4.5 to 3.0.4 in /superset-frontend/packages/superset-ui-core (@dependabot[bot]) +- [#31542](https://github.com/apache/superset/pull/31542) chore(deps): bump @docsearch/react from 3.6.3 to 3.8.2 in /docs (@dependabot[bot]) +- [#31225](https://github.com/apache/superset/pull/31225) chore(deps-dev): bump typescript from 4.9.5 to 5.7.2 in /superset-frontend/packages/superset-ui-demo (@dependabot[bot]) +- [#31388](https://github.com/apache/superset/pull/31388) chore(deps): update dompurify requirement from ^3.1.3 to ^3.2.3 in /superset-frontend/plugins/legacy-preset-chart-nvd3 (@dependabot[bot]) +- [#31543](https://github.com/apache/superset/pull/31543) chore(deps): bump @storybook/types from 8.1.11 to 8.4.7 in /superset-frontend/packages/superset-ui-demo (@dependabot[bot]) +- [#31533](https://github.com/apache/superset/pull/31533) chore(deps): update chalk requirement from ^5.3.0 to ^5.4.0 in /superset-frontend/packages/generator-superset (@dependabot[bot]) +- [#31532](https://github.com/apache/superset/pull/31532) chore(deps-dev): update @types/d3-time requirement from ^3.0.3 to ^3.0.4 in /superset-frontend/packages/superset-ui-core (@dependabot[bot]) +- [#31531](https://github.com/apache/superset/pull/31531) chore(deps): update yeoman-generator requirement from ^7.3.2 to ^7.4.0 in /superset-frontend/packages/generator-superset (@dependabot[bot]) +- [#31525](https://github.com/apache/superset/pull/31525) chore(deps): update @deck.gl/layers requirement from ^9.0.37 to ^9.0.38 in /superset-frontend/plugins/legacy-preset-chart-deckgl (@dependabot[bot]) +- [#31524](https://github.com/apache/superset/pull/31524) chore(deps-dev): update @babel/types requirement from ^7.25.6 to ^7.26.3 in /superset-frontend/plugins/plugin-chart-pivot-table (@dependabot[bot]) +- [#31389](https://github.com/apache/superset/pull/31389) chore(deps): update @emotion/styled requirement from ^11.3.0 to ^11.14.0 in /superset-frontend/packages/superset-ui-demo (@dependabot[bot]) +- [#31519](https://github.com/apache/superset/pull/31519) chore: remove dependency on func_timeout because LGPL (@mistercrunch) +- [#31517](https://github.com/apache/superset/pull/31517) chore: update browser list (@mistercrunch) +- [#31420](https://github.com/apache/superset/pull/31420) refactor(Modal): Upgrade Modal component to Antd5 (@alexandrusoare) +- [#31511](https://github.com/apache/superset/pull/31511) chore: rename `apply_post_process` (@betodealmeida) +- [#31390](https://github.com/apache/superset/pull/31390) chore(gha): bump ubuntu to latest fresh release (@mistercrunch) +- [#31313](https://github.com/apache/superset/pull/31313) chore: deprecate pip-compile-multi in favor or uv (@mistercrunch) +- [#31515](https://github.com/apache/superset/pull/31515) chore: deprecate fossa in favor of liccheck to validate python licenses (@mistercrunch) +- [#31501](https://github.com/apache/superset/pull/31501) chore(code owners): Update CODEOWNERS file to remove a couple inactive contributors (@rusackas) +- [#31496](https://github.com/apache/superset/pull/31496) docs: Update new user for Careem to user's list (@samraHanif0340) +- [#31451](https://github.com/apache/superset/pull/31451) chore: remove numba and llvmlite deps as they are large and we don't use them (@mistercrunch) +- [#30605](https://github.com/apache/superset/pull/30605) chore(translations): German translation update (@gerbermichi) +- [#31262](https://github.com/apache/superset/pull/31262) chore: deprecate `pylint` in favor of `ruff` (@mistercrunch) +- [#31422](https://github.com/apache/superset/pull/31422) docs: CVEs fixed on 4.1.0 v2 (@dpgaspar) +- [#31268](https://github.com/apache/superset/pull/31268) refactor: Migrate AdhocFilterEditPopoverSqlTabContent to TypeScript (@EnxDev) +- [#30196](https://github.com/apache/superset/pull/30196) build(packages): npm build/publish improvements. Making packages publishable again. (@rusackas) +- [#31378](https://github.com/apache/superset/pull/31378) chore(deps): bump nanoid from 3.3.7 to 3.3.8 in /docs (@dependabot[bot]) +- [#31381](https://github.com/apache/superset/pull/31381) chore(embedded sdk): bump sdk version number (@rusackas) +- [#31380](https://github.com/apache/superset/pull/31380) chore(embedded sdk): bumping dependencies (@rusackas) +- [#31362](https://github.com/apache/superset/pull/31362) chore(deps): bump nanoid from 5.0.7 to 5.0.9 in /superset-frontend/cypress-base (@dependabot[bot]) +- [#31209](https://github.com/apache/superset/pull/31209) chore(deps): bump antd from 5.21.6 to 5.22.2 in /docs (@dependabot[bot]) +- [#31219](https://github.com/apache/superset/pull/31219) chore(deps-dev): bump esbuild from 0.20.0 to 0.24.0 in /superset-frontend (@dependabot[bot]) +- [#31314](https://github.com/apache/superset/pull/31314) chore(deps): bump path-to-regexp and express in /superset-websocket/utils/client-ws-app (@dependabot[bot]) +- [#31220](https://github.com/apache/superset/pull/31220) chore(deps): bump winston from 3.15.0 to 3.17.0 in /superset-websocket (@dependabot[bot]) +- [#31218](https://github.com/apache/superset/pull/31218) chore(deps-dev): bump @babel/eslint-parser from 7.23.10 to 7.25.9 in /superset-frontend (@dependabot[bot]) +- [#31222](https://github.com/apache/superset/pull/31222) chore(deps-dev): bump @eslint/js from 9.14.0 to 9.16.0 in /superset-websocket (@dependabot[bot]) +- [#31352](https://github.com/apache/superset/pull/31352) docs: CVEs fixed on 4.1.0 (@dpgaspar) +- [#31168](https://github.com/apache/superset/pull/31168) refactor(Alert): Migrate Alert component to Ant Design V5 (@LevisNgigi) +- [#31290](https://github.com/apache/superset/pull/31290) chore(FilterBar): move the "Add/edit filters" button in the FilterBar to the settings menu (@alexandrusoare) +- [#31312](https://github.com/apache/superset/pull/31312) refactor(Name_column): Make 'Name' column of Saved Query page into links (@LevisNgigi) +- [#31203](https://github.com/apache/superset/pull/31203) chore(deps): bump deck.gl from 9.0.34 to 9.0.36 in /superset-frontend/plugins/legacy-preset-chart-deckgl (@dependabot[bot]) +- [#31275](https://github.com/apache/superset/pull/31275) chore: relax greenlet requirements (@sadpandajoe) +- [#31205](https://github.com/apache/superset/pull/31205) chore(deps-dev): bump typescript from 5.6.3 to 5.7.2 in /docs (@dependabot[bot]) +- [#31207](https://github.com/apache/superset/pull/31207) chore(deps): bump @algolia/client-search from 5.12.0 to 5.15.0 in /docs (@dependabot[bot]) +- [#31208](https://github.com/apache/superset/pull/31208) chore(deps): bump less from 4.2.0 to 4.2.1 in /docs (@dependabot[bot]) +- [#31204](https://github.com/apache/superset/pull/31204) chore(deps-dev): bump @docusaurus/tsconfig from 3.5.2 to 3.6.3 in /docs (@dependabot[bot]) +- [#31206](https://github.com/apache/superset/pull/31206) chore(deps): bump swagger-ui-react from 5.17.14 to 5.18.2 in /docs (@dependabot[bot]) +- [#31224](https://github.com/apache/superset/pull/31224) chore(deps-dev): bump @types/jest from 29.5.12 to 29.5.14 in /superset-websocket (@dependabot[bot]) +- [#31228](https://github.com/apache/superset/pull/31228) chore(deps): bump @types/react-table from 7.7.19 to 7.7.20 in /superset-frontend (@dependabot[bot]) +- [#31210](https://github.com/apache/superset/pull/31210) chore(deps-dev): bump @docusaurus/module-type-aliases from 3.5.2 to 3.6.3 in /docs (@dependabot[bot]) +- [#31213](https://github.com/apache/superset/pull/31213) chore(deps): bump @ant-design/icons from 5.5.1 to 5.5.2 in /docs (@dependabot[bot]) +- [#31230](https://github.com/apache/superset/pull/31230) chore(deps): bump @scarf/scarf from 1.3.0 to 1.4.0 in /superset-frontend (@dependabot[bot]) +- [#31259](https://github.com/apache/superset/pull/31259) chore(bug report template): bump Superset versions to reflect 4.1.1 release (@sfirke) +- [#31231](https://github.com/apache/superset/pull/31231) chore(deps): bump re-resizable from 6.10.0 to 6.10.1 in /superset-frontend (@dependabot[bot]) +- [#31270](https://github.com/apache/superset/pull/31270) refactor: Split SliceHeaderControls into smaller files (@kgabryje) +- [#30864](https://github.com/apache/superset/pull/30864) docs: adapt docs to suggest 'docker compose up --build' (@mistercrunch) +- [#31034](https://github.com/apache/superset/pull/31034) chore: simplify Dockerfile package install calls with bash wrappers (@mistercrunch) +- [#31214](https://github.com/apache/superset/pull/31214) chore(deps): bump codecov/codecov-action from 4 to 5 (@dependabot[bot]) +- [#31250](https://github.com/apache/superset/pull/31250) chore(🦾): bump python flask-migrate subpackage(s) (@github-actions[bot]) +- [#31249](https://github.com/apache/superset/pull/31249) chore(🦾): bump python nh3 0.2.18 -> 0.2.19 (@github-actions[bot]) +- [#31253](https://github.com/apache/superset/pull/31253) chore(🦾): bump python pyjwt 2.10.0 -> 2.10.1 (@github-actions[bot]) +- [#31254](https://github.com/apache/superset/pull/31254) chore: pin greenlet in base dependencies (@mistercrunch) +- [#31186](https://github.com/apache/superset/pull/31186) docs(contributing): how to nuke the docker-compose postgres (@mistercrunch) +- [#31244](https://github.com/apache/superset/pull/31244) perf: Optimize DashboardPage and SyncDashboardState (@kgabryje) +- [#31243](https://github.com/apache/superset/pull/31243) perf: Optimize native filters and cross filters (@kgabryje) +- [#31240](https://github.com/apache/superset/pull/31240) perf: Optimize dashboard grid components (@kgabryje) +- [#31242](https://github.com/apache/superset/pull/31242) perf: Optimize Dashboard components (@kgabryje) +- [#31241](https://github.com/apache/superset/pull/31241) perf: Optimize dashboard chart-related components (@kgabryje) +- [#31182](https://github.com/apache/superset/pull/31182) chore(Tooltip): Upgrade Tooltip to Ant Design 5 (@alexandrusoare) +- [#31193](https://github.com/apache/superset/pull/31193) refactor: Creates the VizType enum (@michael-s-molina) +- [#31165](https://github.com/apache/superset/pull/31165) docs: update slack alert instructions to work with V2 slack API (@PJDuszynski) +- [#28461](https://github.com/apache/superset/pull/28461) chore(🦾): bump python sqlglot 23.6.3 -> 23.15.8 (@github-actions[bot]) +- [#31171](https://github.com/apache/superset/pull/31171) chore(🦾): bump python pyparsing 3.1.2 -> 3.2.0 (@github-actions[bot]) +- [#31170](https://github.com/apache/superset/pull/31170) chore(deps): cap async_timeout<5.0.0 (@mistercrunch) +- [#31032](https://github.com/apache/superset/pull/31032) refactor: remove more sqlparse (@betodealmeida) +- [#31126](https://github.com/apache/superset/pull/31126) chore(🦾): bump python importlib-metadata 7.1.0 -> 8.5.0 (@github-actions[bot]) +- [#29382](https://github.com/apache/superset/pull/29382) chore: deprecate tox in favor of act (@mistercrunch) +- [#31109](https://github.com/apache/superset/pull/31109) chore(🦾): bump python billiard 4.2.0 -> 4.2.1 (@github-actions[bot]) +- [#31138](https://github.com/apache/superset/pull/31138) chore(🦾): bump python flask-limiter 3.7.0 -> 3.8.0 (@github-actions[bot]) +- [#31140](https://github.com/apache/superset/pull/31140) chore(🦾): bump python mako 1.3.5 -> 1.3.6 (@github-actions[bot]) +- [#31127](https://github.com/apache/superset/pull/31127) chore(🦾): bump python celery subpackage(s) (@github-actions[bot]) +- [#31128](https://github.com/apache/superset/pull/31128) chore(🦾): bump python humanize 4.9.0 -> 4.11.0 (@github-actions[bot]) +- [#31129](https://github.com/apache/superset/pull/31129) chore(🦾): bump python simplejson 3.19.2 -> 3.19.3 (@github-actions[bot]) +- [#31130](https://github.com/apache/superset/pull/31130) chore(🦾): bump python numexpr 2.10.1 -> 2.10.2 (@github-actions[bot]) +- [#31132](https://github.com/apache/superset/pull/31132) chore(🦾): bump python slack-sdk 3.27.2 -> 3.33.4 (@github-actions[bot]) +- [#31133](https://github.com/apache/superset/pull/31133) chore(🦾): bump python pyopenssl 24.1.0 -> 24.2.1 (@github-actions[bot]) +- [#31135](https://github.com/apache/superset/pull/31135) chore(🦾): bump python dnspython 2.6.1 -> 2.7.0 (@github-actions[bot]) +- [#31136](https://github.com/apache/superset/pull/31136) chore(🦾): bump python zstandard 0.22.0 -> 0.23.0 (@github-actions[bot]) +- [#31137](https://github.com/apache/superset/pull/31137) chore(🦾): bump python limits 3.12.0 -> 3.13.0 (@github-actions[bot]) +- [#31139](https://github.com/apache/superset/pull/31139) chore(🦾): bump python flask-jwt-extended 4.6.0 -> 4.7.1 (@github-actions[bot]) +- [#31125](https://github.com/apache/superset/pull/31125) chore(🦾): bump python gunicorn 22.0.0 -> 23.0.0 (@github-actions[bot]) +- [#31124](https://github.com/apache/superset/pull/31124) chore(🦾): bump python zipp 3.19.0 -> 3.21.0 (@github-actions[bot]) +- [#31123](https://github.com/apache/superset/pull/31123) chore(🦾): bump python flask-compress 1.15 -> 1.17 (@github-actions[bot]) +- [#31108](https://github.com/apache/superset/pull/31108) chore(🦾): bump python dill 0.3.8 -> 0.3.9 (@github-actions[bot]) +- [#31116](https://github.com/apache/superset/pull/31116) chore(🦾): bump python email-validator 2.1.1 -> 2.2.0 (@github-actions[bot]) +- [#31153](https://github.com/apache/superset/pull/31153) chore(asf): add `gh-pages` to protected branches (@rusackas) +- [#31122](https://github.com/apache/superset/pull/31122) chore(🦾): bump python async-timeout 4.0.3 -> 5.0.1 (@github-actions[bot]) +- [#31121](https://github.com/apache/superset/pull/31121) chore(🦾): bump python prompt-toolkit 3.0.44 -> 3.0.48 (@github-actions[bot]) +- [#31119](https://github.com/apache/superset/pull/31119) chore(🦾): bump python sqlparse 0.5.0 -> 0.5.2 (@github-actions[bot]) +- [#30963](https://github.com/apache/superset/pull/30963) refactor(List): Upgrade List from antdesign4 to antdesign5 (@alexandrusoare) +- [#31113](https://github.com/apache/superset/pull/31113) chore(🦾): bump python mysqlclient 2.2.4 -> 2.2.6 (@github-actions[bot]) +- [#31114](https://github.com/apache/superset/pull/31114) chore(🦾): bump python grpcio-status subpackage(s) (@github-actions[bot]) +- [#31112](https://github.com/apache/superset/pull/31112) chore(🦾): bump python cycler 0.11.0 -> 0.12.1 (@github-actions[bot]) +- [#31091](https://github.com/apache/superset/pull/31091) chore(🦾): bump python croniter 2.0.5 -> 5.0.1 (@github-actions[bot]) +- [#31107](https://github.com/apache/superset/pull/31107) chore(🦾): bump python google-auth 2.29.0 -> 2.36.0 (@github-actions[bot]) +- [#31106](https://github.com/apache/superset/pull/31106) chore(🦾): bump python psutil 6.0.0 -> 6.1.0 (@github-actions[bot]) +- [#31105](https://github.com/apache/superset/pull/31105) chore(🦾): bump python dnspython 2.6.1 -> 2.7.0 (@github-actions[bot]) +- [#31102](https://github.com/apache/superset/pull/31102) chore(🦾): bump python markdown 3.6 -> 3.7 (@github-actions[bot]) +- [#31101](https://github.com/apache/superset/pull/31101) chore(🦾): bump python pluggy 1.4.0 -> 1.5.0 (@github-actions[bot]) +- [#31100](https://github.com/apache/superset/pull/31100) chore(🦾): bump python sqloxide 0.1.43 -> 0.1.51 (@github-actions[bot]) +- [#31099](https://github.com/apache/superset/pull/31099) chore(🦾): bump python wheel 0.43.0 -> 0.45.1 (@github-actions[bot]) +- [#31098](https://github.com/apache/superset/pull/31098) chore(🦾): bump python pyproject-api 1.6.1 -> 1.8.0 (@github-actions[bot]) +- [#31096](https://github.com/apache/superset/pull/31096) chore(🦾): bump python pytest-cov 5.0.0 -> 6.0.0 (@github-actions[bot]) +- [#31094](https://github.com/apache/superset/pull/31094) chore(🦾): bump python chardet 5.1.0 -> 5.2.0 (@github-actions[bot]) +- [#31093](https://github.com/apache/superset/pull/31093) chore(🦾): bump python jsonpath-ng 1.6.1 -> 1.7.0 (@github-actions[bot]) +- [#31092](https://github.com/apache/superset/pull/31092) chore(🦾): bump python sshtunnel subpackage(s) (@github-actions[bot]) +- [#31097](https://github.com/apache/superset/pull/31097) chore(🦾): bump python mako 1.3.5 -> 1.3.6 (@github-actions[bot]) +- [#31090](https://github.com/apache/superset/pull/31090) chore(🦾): bump python tomlkit 0.12.5 -> 0.13.2 (@github-actions[bot]) +- [#31087](https://github.com/apache/superset/pull/31087) chore(🦾): bump python isodate 0.6.1 -> 0.7.2 (@github-actions[bot]) +- [#31082](https://github.com/apache/superset/pull/31082) chore(🦾): bump python db-dtypes 1.2.0 -> 1.3.1 (@github-actions[bot]) +- [#31081](https://github.com/apache/superset/pull/31081) chore(🦾): bump python trino 0.328.0 -> 0.330.0 (@github-actions[bot]) +- [#31089](https://github.com/apache/superset/pull/31089) chore(🦾): bump python certifi 2024.2.2 -> 2024.8.30 (@github-actions[bot]) +- [#31088](https://github.com/apache/superset/pull/31088) chore(🦾): bump python pydata-google-auth 1.7.0 -> 1.9.0 (@github-actions[bot]) +- [#31086](https://github.com/apache/superset/pull/31086) chore(🦾): bump python pyproject-hooks 1.0.0 -> 1.2.0 (@github-actions[bot]) +- [#31085](https://github.com/apache/superset/pull/31085) chore(🦾): bump python sqlalchemy-bigquery 1.11.0 -> 1.12.0 (@github-actions[bot]) +- [#31084](https://github.com/apache/superset/pull/31084) chore(🦾): bump python kiwisolver 1.4.5 -> 1.4.7 (@github-actions[bot]) +- [#31083](https://github.com/apache/superset/pull/31083) chore(🦾): bump python coverage subpackage(s) (@github-actions[bot]) +- [#31077](https://github.com/apache/superset/pull/31077) chore(🦾): bump python cfgv 3.3.1 -> 3.4.0 (@github-actions[bot]) +- [#31075](https://github.com/apache/superset/pull/31075) chore(🦾): bump python fonttools 4.51.0 -> 4.55.0 (@github-actions[bot]) +- [#31076](https://github.com/apache/superset/pull/31076) chore(🦾): bump python pyasn1-modules 0.4.0 -> 0.4.1 (@github-actions[bot]) +- [#31079](https://github.com/apache/superset/pull/31079) chore(🦾): bump python pyhive subpackage(s) (@github-actions[bot]) +- [#31078](https://github.com/apache/superset/pull/31078) chore(🦾): bump python google-cloud-core 2.3.2 -> 2.4.1 (@github-actions[bot]) +- [#31048](https://github.com/apache/superset/pull/31048) chore(🦾): bump python sqlalchemy-utils subpackage(s) (@github-actions[bot]) +- [#31073](https://github.com/apache/superset/pull/31073) chore(🦾): bump python amqp 5.2.0 -> 5.3.1 (@github-actions[bot]) +- [#31071](https://github.com/apache/superset/pull/31071) chore(🦾): bump python cachetools 5.3.3 -> 5.5.0 (@github-actions[bot]) +- [#31074](https://github.com/apache/superset/pull/31074) chore(🦾): bump python kombu 5.3.7 -> 5.4.2 (@github-actions[bot]) +- [#31066](https://github.com/apache/superset/pull/31066) chore(🦾): bump python pyyaml 6.0.1 -> 6.0.2 (@github-actions[bot]) +- [#31068](https://github.com/apache/superset/pull/31068) chore(🦾): bump python tqdm 4.66.4 -> 4.67.1 (@github-actions[bot]) +- [#31069](https://github.com/apache/superset/pull/31069) chore(🦾): bump python proto-plus 1.22.2 -> 1.25.0 (@github-actions[bot]) +- [#31067](https://github.com/apache/superset/pull/31067) chore(🦾): bump python importlib-resources 6.4.0 -> 6.4.5 (@github-actions[bot]) +- [#31062](https://github.com/apache/superset/pull/31062) chore(🦾): bump python apispec subpackage(s) (@github-actions[bot]) +- [#31056](https://github.com/apache/superset/pull/31056) chore(🦾): bump python deprecated 1.2.14 -> 1.2.15 (@github-actions[bot]) +- [#31050](https://github.com/apache/superset/pull/31050) chore(🦾): bump python pre-commit 3.7.1 -> 4.0.1 (@github-actions[bot]) +- [#31064](https://github.com/apache/superset/pull/31064) chore(🦾): bump python charset-normalizer 3.3.2 -> 3.4.0 (@github-actions[bot]) +- [#31001](https://github.com/apache/superset/pull/31001) chore(🦾): bump python ruff 0.4.5 -> 0.8.0 (@github-actions[bot]) +- [#31049](https://github.com/apache/superset/pull/31049) chore(🦾): bump python googleapis-common-protos 1.63.0 -> 1.66.0 (@github-actions[bot]) +- [#31046](https://github.com/apache/superset/pull/31046) chore(🦾): bump python cron-descriptor 1.4.3 -> 1.4.5 (@github-actions[bot]) +- [#31052](https://github.com/apache/superset/pull/31052) chore(🦾): bump python flask-wtf 1.2.1 -> 1.2.2 (@github-actions[bot]) +- [#31044](https://github.com/apache/superset/pull/31044) docs: updated the install process in pypi.mdx (@Rkejji) +- [#31054](https://github.com/apache/superset/pull/31054) chore(🦾): bump python nh3 0.2.17 -> 0.2.18 (@github-actions[bot]) +- [#31045](https://github.com/apache/superset/pull/31045) chore(🦾): bump python marshmallow 3.21.2 -> 3.23.1 (@github-actions[bot]) +- [#31041](https://github.com/apache/superset/pull/31041) chore(🦾): bump python idna 3.7 -> 3.10 (@github-actions[bot]) +- [#31042](https://github.com/apache/superset/pull/31042) chore(🦾): bump python pyjwt 2.8.0 -> 2.10.0 (@github-actions[bot]) +- [#31040](https://github.com/apache/superset/pull/31040) chore(🦾): bump python et-xmlfile 1.1.0 -> 2.0.0 & remove pyhive[hive] from requirements/development.in (@github-actions[bot]) +- [#30651](https://github.com/apache/superset/pull/30651) chore(legacy-plugin-chart-map-box): replace viewport-mercator-project with @math.gl/web-mercator (@birkskyum) +- [#31004](https://github.com/apache/superset/pull/31004) chore(🦾): bump python pandas subpackage(s) (@github-actions[bot]) +- [#31030](https://github.com/apache/superset/pull/31030) chore: Cleanup code related to MetadataBar, fix types (@kgabryje) +- [#31029](https://github.com/apache/superset/pull/31029) chore: Refactor dashboard header to func component (@kgabryje) +- [#30998](https://github.com/apache/superset/pull/30998) chore(🦾): bump python cattrs 23.2.3 -> 24.1.2 (@github-actions[bot]) +- [#30867](https://github.com/apache/superset/pull/30867) docs: Update doc about CSV upload (@seiyab) +- [#30972](https://github.com/apache/superset/pull/30972) docs: Embedded sdk (@jpchev) +- [#30981](https://github.com/apache/superset/pull/30981) chore: publish wheels (@dimbleby) +- [#31000](https://github.com/apache/superset/pull/31000) chore(🦾): bump python flask-babel subpackage(s) (@github-actions[bot]) +- [#31002](https://github.com/apache/superset/pull/31002) chore(🦾): bump python cffi 1.16.0 -> 1.17.1 (@github-actions[bot]) +- [#31006](https://github.com/apache/superset/pull/31006) chore(🦾): bump python numexpr 2.10.0 -> 2.10.1 (@github-actions[bot]) +- [#31021](https://github.com/apache/superset/pull/31021) chore: add unit tests for `is_mutating()` (@betodealmeida) +- [#30918](https://github.com/apache/superset/pull/30918) chore(helm): bumping app version to 4.1.1 in helm chart (@lodu) +- [#30948](https://github.com/apache/superset/pull/30948) chore: add performance information to tooltip (@eschutho) +- [#30970](https://github.com/apache/superset/pull/30970) build(deps): bump cross-spawn from 7.0.3 to 7.0.6 in /docs (@dependabot[bot]) +- [#30969](https://github.com/apache/superset/pull/30969) build(deps): bump cross-spawn from 7.0.3 to 7.0.6 in /superset-frontend/cypress-base (@dependabot[bot]) +- [#30818](https://github.com/apache/superset/pull/30818) chore(Accessibility): Fix accessibility for 'Show x entries' dropdown in tables (@LevisNgigi) +- [#30946](https://github.com/apache/superset/pull/30946) chore(docs): Update list of supported databases to include CrateDB (@amotl) +- [#30915](https://github.com/apache/superset/pull/30915) chore: update change log, UPDATING.md and bug-report.yml for 4.1 release (@sadpandajoe) +- [#29243](https://github.com/apache/superset/pull/29243) chore(deps): Migrate from `crate[sqlalchemy]` to `sqlalchemy-cratedb` (@amotl) +- [#30930](https://github.com/apache/superset/pull/30930) docs: add Free2Move to INTHEWILD.md (@PaoloTerzi) +- [#30925](https://github.com/apache/superset/pull/30925) chore(ci): add tai and michael to helm owners (@villebro) +- [#30730](https://github.com/apache/superset/pull/30730) refactor(input): Migrate Input component to Ant Design 5 (@msyavuz) +- [#30740](https://github.com/apache/superset/pull/30740) refactor(Avatar): Migrate Avatar to Ant Design 5 (@msyavuz) +- [#30806](https://github.com/apache/superset/pull/30806) build(deps): bump remark-gfm from 3.0.1 to 4.0.0 in /superset-frontend (@dependabot[bot]) +- [#29545](https://github.com/apache/superset/pull/29545) chore(AntD5): touchup on component imports/exports, theming ListViewCard (@rusackas) +- [#30775](https://github.com/apache/superset/pull/30775) chore: update help text copy on dataset settings (@yousoph) +- [#30792](https://github.com/apache/superset/pull/30792) build(deps): bump @algolia/client-search from 4.24.0 to 5.12.0 in /docs (@dependabot[bot]) +- [#30770](https://github.com/apache/superset/pull/30770) docs: make it more clear that GLOBAL_ASYNC_QUERIES is experimental/beta (@mistercrunch) +- [#30883](https://github.com/apache/superset/pull/30883) perf: Prevent redundant calls to getRelevantDataMask (@kgabryje) +- [#30847](https://github.com/apache/superset/pull/30847) chore(GHA): Making the Linkinator STEP non-blocking, rather than the JOB. (@rusackas) +- [#30812](https://github.com/apache/superset/pull/30812) chore(FilterBar): Filter bar accessibility (@alexandrusoare) +- [#30854](https://github.com/apache/superset/pull/30854) chore: Chart context menu permissions cleanup (@kgabryje) +- [#30255](https://github.com/apache/superset/pull/30255) chore(scripts): purge node_modules folder on `npm prune` (@rusackas) +- [#30846](https://github.com/apache/superset/pull/30846) chore(actions): Bump Linkinator in superset-docs-verify.yml (@rusackas) +- [#30797](https://github.com/apache/superset/pull/30797) build(deps): bump @docsearch/react from 3.6.2 to 3.6.3 in /docs (@dependabot[bot]) +- [#30796](https://github.com/apache/superset/pull/30796) build(deps): bump @mdx-js/react from 3.0.1 to 3.1.0 in /docs (@dependabot[bot]) +- [#30793](https://github.com/apache/superset/pull/30793) build(deps-dev): bump @types/react from 18.3.10 to 18.3.12 in /docs (@dependabot[bot]) +- [#30795](https://github.com/apache/superset/pull/30795) build(deps-dev): bump typescript from 5.6.2 to 5.6.3 in /docs (@dependabot[bot]) +- [#30799](https://github.com/apache/superset/pull/30799) build(deps): bump @saucelabs/theme-github-codeblock from 0.2.3 to 0.3.0 in /docs (@dependabot[bot]) +- [#30824](https://github.com/apache/superset/pull/30824) docs: Update INTHEWILD.md with 2070Health Org (@sanjaynayak007) +- [#30838](https://github.com/apache/superset/pull/30838) chore: Revert "build(deps): bump JustinBeckwith/linkinator-action from 1.10.4 to 1.11.0" (@rusackas) +- [#30832](https://github.com/apache/superset/pull/30832) build(deps-dev): bump webpack from 5.95.0 to 5.96.1 in /docs (@dependabot[bot]) +- [#30822](https://github.com/apache/superset/pull/30822) docs: Update INTHEWILD.md (@Habeeb556) +- [#30835](https://github.com/apache/superset/pull/30835) build(deps-dev): bump eslint from 9.11.0 to 9.14.0 in /superset-websocket (@dependabot[bot]) +- [#30782](https://github.com/apache/superset/pull/30782) build(deps): bump uuid from 10.0.0 to 11.0.2 in /superset-websocket (@dependabot[bot]) +- [#30784](https://github.com/apache/superset/pull/30784) build(deps): bump winston from 3.13.0 to 3.15.0 in /superset-websocket (@dependabot[bot]) +- [#30786](https://github.com/apache/superset/pull/30786) build(deps): bump deck.gl from 9.0.28 to 9.0.34 in /superset-frontend/plugins/legacy-preset-chart-deckgl (@dependabot[bot]) +- [#30803](https://github.com/apache/superset/pull/30803) build(deps-dev): bump eslint-plugin-react from 7.33.2 to 7.37.2 in /superset-frontend (@dependabot[bot]) +- [#30781](https://github.com/apache/superset/pull/30781) build(deps-dev): bump typescript-eslint from 8.8.0 to 8.12.2 in /superset-websocket (@dependabot[bot]) +- [#30809](https://github.com/apache/superset/pull/30809) build(deps-dev): bump prettier-plugin-packagejson from 2.5.2 to 2.5.3 in /superset-frontend (@dependabot[bot]) +- [#30817](https://github.com/apache/superset/pull/30817) build(deps): bump webpack from 5.80.0 to 5.96.1 in /superset-frontend/cypress-base (@dependabot[bot]) +- [#30794](https://github.com/apache/superset/pull/30794) build(deps): bump antd from 5.20.5 to 5.21.6 in /docs (@dependabot[bot]) +- [#30811](https://github.com/apache/superset/pull/30811) build(deps): bump @rjsf/validator-ajv8 from 5.19.4 to 5.22.3 in /superset-frontend (@dependabot[bot]) +- [#30804](https://github.com/apache/superset/pull/30804) build(deps): bump ace-builds from 1.35.4 to 1.36.3 in /superset-frontend (@dependabot[bot]) +- [#30810](https://github.com/apache/superset/pull/30810) build(deps-dev): bump eslint-plugin-testing-library from 6.2.2 to 6.4.0 in /superset-frontend (@dependabot[bot]) +- [#30805](https://github.com/apache/superset/pull/30805) build(deps-dev): bump eslint-import-resolver-typescript from 3.6.1 to 3.6.3 in /superset-frontend (@dependabot[bot]) +- [#30802](https://github.com/apache/superset/pull/30802) build(deps): bump JustinBeckwith/linkinator-action from 1.10.4 to 1.11.0 (@dependabot[bot]) +- [#30758](https://github.com/apache/superset/pull/30758) style(databases-upload-form): update Upload Form cosmetics (@vine-trellis) +- [#30697](https://github.com/apache/superset/pull/30697) refactor: Migrate SliceAdder to typescript (@EnxDev) +- [#30731](https://github.com/apache/superset/pull/30731) refactor(Switch): Upgrade Switch to Ant Design 5 (@alexandrusoare) +- [#30757](https://github.com/apache/superset/pull/30757) docs: Adding link to StarRocks official docs (@rusackas) +- [#30747](https://github.com/apache/superset/pull/30747) docs: Update INTHEWILD.md (@MSTartan) +- [#30753](https://github.com/apache/superset/pull/30753) docs: add Sarathi to users list (@SaiSkandaTNI) +- [#30749](https://github.com/apache/superset/pull/30749) docs: Update INTHEWILD.md with Medic (@1yuv) +- [#30355](https://github.com/apache/superset/pull/30355) chore(fe): replace deprecate aliased Jest matchers with corresponding substituents (@hainenber) +- [#30536](https://github.com/apache/superset/pull/30536) build(deps): bump cookie from 0.6.0 to 0.7.0 in /superset-websocket (@dependabot[bot]) +- [#30480](https://github.com/apache/superset/pull/30480) build(deps-dev): bump webpack from 5.94.0 to 5.95.0 in /docs (@dependabot[bot]) +- [#30571](https://github.com/apache/superset/pull/30571) build(deps): bump cookie, cookie-parser and express in /superset-websocket/utils/client-ws-app (@dependabot[bot]) +- [#30738](https://github.com/apache/superset/pull/30738) docs: rename Twitter to X in the INTHEWILD.md (@wugeer) +- [#30743](https://github.com/apache/superset/pull/30743) docs(templating): Replace "true" with "1 = 1" and explain its purpose (@sfirke) +- [#30709](https://github.com/apache/superset/pull/30709) build(deps-dev): bump http-proxy-middleware from 2.0.6 to 2.0.7 in /superset-frontend (@dependabot[bot]) +- [#30654](https://github.com/apache/superset/pull/30654) refactor: Migrate UndoRedoKeyListeners to typescript (@EnxDev) +- [#30653](https://github.com/apache/superset/pull/30653) refactor: Migration publishedStatus to typescript (@EnxDev) +- [#30683](https://github.com/apache/superset/pull/30683) build(deps): bump http-proxy-middleware from 2.0.6 to 2.0.7 in /docs (@dependabot[bot]) +- [#30568](https://github.com/apache/superset/pull/30568) refactor: Migrate HeaderActionsDropdown to typescript (@EnxDev) +- [#30655](https://github.com/apache/superset/pull/30655) docs: frontend long build time (@CodeWithEmad) +- [#30662](https://github.com/apache/superset/pull/30662) refactor: Split FastVizSwitcher into multiple files for readability (@kgabryje) +- [#30609](https://github.com/apache/superset/pull/30609) refactor(Dashboard): Native filters form update endpoint (@geido) +- [#30613](https://github.com/apache/superset/pull/30613) chore: Enable suppressing default chart context menu (@kgabryje) +- [#30523](https://github.com/apache/superset/pull/30523) docs: Clarification on which command to use on which Ubuntu version. (@kkovacs) +- [#30599](https://github.com/apache/superset/pull/30599) chore(number-formatter): upgrade pretty-ms to 9.1.0 (@villebro) +- [#30572](https://github.com/apache/superset/pull/30572) build(deps): bump cookie, @applitools/eyes-storybook and express in /superset-frontend (@dependabot[bot]) +- [#30357](https://github.com/apache/superset/pull/30357) chore(fe): uplift FE packages to latest version (@hainenber) +- [#30521](https://github.com/apache/superset/pull/30521) chore: enable lint PT009 'use regular assert over self.assert.\*' (@mistercrunch) +- [#28370](https://github.com/apache/superset/pull/28370) refactor: Migration of Chart to TypeScript (@EnxDev) +- [#30528](https://github.com/apache/superset/pull/30528) chore(fe): bump webpack-related packages to v5 (@hainenber) +- [#30526](https://github.com/apache/superset/pull/30526) chore(translations): Slovenian translation update (@dkrat7) +- [#30495](https://github.com/apache/superset/pull/30495) chore: add native filters to Covid Vaccines dashboard (@sadpandajoe) +- [#30463](https://github.com/apache/superset/pull/30463) build(deps-dev): bump typescript from 5.5.4 to 5.6.2 in /superset-websocket (@dependabot[bot]) +- [#30472](https://github.com/apache/superset/pull/30472) build(deps): bump express from 4.20.0 to 4.21.0 in /superset-websocket/utils/client-ws-app (@dependabot[bot]) +- [#30496](https://github.com/apache/superset/pull/30496) docs: fix broken links in CI (@mistercrunch) +- [#30476](https://github.com/apache/superset/pull/30476) build(deps-dev): bump typescript from 5.5.4 to 5.6.2 in /docs (@dependabot[bot]) +- [#30461](https://github.com/apache/superset/pull/30461) build(deps): bump @rjsf/core from 5.19.4 to 5.21.1 in /superset-frontend (@dependabot[bot]) +- [#30465](https://github.com/apache/superset/pull/30465) build(deps-dev): bump typescript-eslint from 8.6.0 to 8.8.0 in /superset-websocket (@dependabot[bot]) +- [#30466](https://github.com/apache/superset/pull/30466) build(deps-dev): bump @types/node from 22.0.2 to 22.7.4 in /superset-websocket (@dependabot[bot]) +- [#30467](https://github.com/apache/superset/pull/30467) build(deps): bump @types/prop-types from 15.7.5 to 15.7.13 in /superset-frontend (@dependabot[bot]) +- [#30469](https://github.com/apache/superset/pull/30469) build(deps): bump @types/react-loadable from 5.5.6 to 5.5.11 in /superset-frontend (@dependabot[bot]) +- [#30471](https://github.com/apache/superset/pull/30471) build(deps): bump debug from 4.3.6 to 4.3.7 in /superset-websocket/utils/client-ws-app (@dependabot[bot]) +- [#30281](https://github.com/apache/superset/pull/30281) refactor(frontend): migrate 6 Enzyme-based tests to RTL, part 2 (@hainenber) +- [#30487](https://github.com/apache/superset/pull/30487) build(deps-dev): bump esbuild-loader from 4.1.0 to 4.2.2 in /superset-frontend (@dependabot[bot]) +- [#30460](https://github.com/apache/superset/pull/30460) build(deps-dev): bump eslint-plugin-file-progress from 1.4.0 to 1.5.0 in /superset-frontend (@dependabot[bot]) +- [#30459](https://github.com/apache/superset/pull/30459) build(deps-dev): bump @cypress/react from 5.12.5 to 8.0.2 in /superset-frontend (@dependabot[bot]) +- [#30464](https://github.com/apache/superset/pull/30464) build(deps-dev): bump @typescript-eslint/eslint-plugin from 8.6.0 to 8.8.0 in /superset-websocket (@dependabot[bot]) +- [#30477](https://github.com/apache/superset/pull/30477) build(deps): bump re-resizable from 6.9.11 to 6.10.0 in /superset-frontend (@dependabot[bot]) +- [#30473](https://github.com/apache/superset/pull/30473) build(deps-dev): bump webpack-manifest-plugin from 4.1.1 to 5.0.0 in /superset-frontend (@dependabot[bot]) +- [#30481](https://github.com/apache/superset/pull/30481) build(deps-dev): bump @types/react from 18.3.5 to 18.3.10 in /docs (@dependabot[bot]) +- [#30483](https://github.com/apache/superset/pull/30483) build(deps): bump @docsearch/react from 3.6.1 to 3.6.2 in /docs (@dependabot[bot]) +- [#30484](https://github.com/apache/superset/pull/30484) build(deps): bump handlebars from 4.7.7 to 4.7.8 in /superset-frontend (@dependabot[bot]) +- [#30485](https://github.com/apache/superset/pull/30485) build(deps-dev): bump @types/yargs from 17.0.32 to 17.0.33 in /superset-frontend (@dependabot[bot]) +- [#30445](https://github.com/apache/superset/pull/30445) docs(dashboard): add docs for named and index colors (@villebro) +- [#30410](https://github.com/apache/superset/pull/30410) chore: log warnings for database tables api (@eschutho) +- [#28747](https://github.com/apache/superset/pull/28747) chore: document upper bound for python lib 'holidays' >= 0.26 (@mistercrunch) +- [#30440](https://github.com/apache/superset/pull/30440) chore(Dashboard): Unblock Global Styles (@geido) +- [#30365](https://github.com/apache/superset/pull/30365) chore: add logging for dashboards/get warnings (@eschutho) +- [#30128](https://github.com/apache/superset/pull/30128) chore(View): Remove unnecessary theme view and defer basic styles (@geido) +- [#30407](https://github.com/apache/superset/pull/30407) chore: Merge description and reproduction steps in the issue template (@michael-s-molina) +- [#30305](https://github.com/apache/superset/pull/30305) chore(legacy-plugin-chart-map-box): bump supercluster to v8 (@birkskyum) +- [#30086](https://github.com/apache/superset/pull/30086) build(deps): update @emotion/react requirement from ^11.4.1 to ^11.13.3 in /superset-frontend/packages/superset-ui-demo (@dependabot[bot]) +- [#27827](https://github.com/apache/superset/pull/27827) build(deps): bump @emotion/react from 11.4.1 to 11.11.4 in /superset-frontend (@dependabot[bot]) +- [#28346](https://github.com/apache/superset/pull/28346) refactor: Migration of AnnotationLayerControl to TypeScript (@EnxDev) +- [#30251](https://github.com/apache/superset/pull/30251) build(deps-dev): bump sinon from 18.0.0 to 18.0.1 in /superset-frontend (@dependabot[bot]) +- [#30315](https://github.com/apache/superset/pull/30315) docs: Corrected Dremio connection string (@doernemt) +- [#30352](https://github.com/apache/superset/pull/30352) chore(docs): fix an agreement error in caching docs (@sfirke) +- [#30346](https://github.com/apache/superset/pull/30346) docs: add HANA database logo in README.md (@axuew) +- [#28290](https://github.com/apache/superset/pull/28290) build(deps): update dompurify requirement from ^3.1.0 to ^3.1.2 in /superset-frontend/plugins/legacy-preset-chart-nvd3 (@dependabot[bot]) +- [#30089](https://github.com/apache/superset/pull/30089) build(deps-dev): bump @storybook/react-webpack5 from 8.1.11 to 8.2.9 in /superset-frontend/packages/superset-ui-demo (@dependabot[bot]) +- [#30359](https://github.com/apache/superset/pull/30359) build(websocket): upgrade ESLint to v9 (@hainenber) +- [#30084](https://github.com/apache/superset/pull/30084) build(deps): bump deck.gl from 9.0.24 to 9.0.28 in /superset-frontend/plugins/legacy-preset-chart-deckgl (@dependabot[bot]) +- [#30300](https://github.com/apache/superset/pull/30300) build(deps): bump dompurify from 3.1.0 to 3.1.3 in /superset-frontend (@dependabot[bot]) +- [#30247](https://github.com/apache/superset/pull/30247) build(deps): bump path-to-regexp from 1.8.0 to 1.9.0 in /superset-frontend/cypress-base (@dependabot[bot]) +- [#30337](https://github.com/apache/superset/pull/30337) docs: sql-templating (@torgge) +- [#30333](https://github.com/apache/superset/pull/30333) docs: Update cache.mdx, add needed space (@varfigstar) +- [#30123](https://github.com/apache/superset/pull/30123) chore: correct a typo (@dl57934) +- [#30262](https://github.com/apache/superset/pull/30262) chore: bump cypress to v 11 (@eschutho) +- [#30313](https://github.com/apache/superset/pull/30313) chore(UPDATING.md): Add item to UPDATING describing translations build flag (@martyngigg) +- [#30227](https://github.com/apache/superset/pull/30227) build(deps): bump express from 4.19.2 to 4.20.0 in /docs (@dependabot[bot]) +- [#30032](https://github.com/apache/superset/pull/30032) docs: HTML embedding of charts/dashboards without authentication (@lindner-tj) +- [#30254](https://github.com/apache/superset/pull/30254) style(explore): clarify ambiguously named "sort by" field (@sfirke) +- [#30321](https://github.com/apache/superset/pull/30321) chore(explore): Medium font weight for section headers (@kasiazjc) +- [#30261](https://github.com/apache/superset/pull/30261) chore: remove redundant code (@villebro) +- [#25910](https://github.com/apache/superset/pull/25910) chore(deps): bump dremio deps (@gnought) +- [#30268](https://github.com/apache/superset/pull/30268) docs: Update kubernetes.mdx (@nyandajr) +- [#29771](https://github.com/apache/superset/pull/29771) chore(docker): move mysql os-level deps (GPL) to dev image only (@mistercrunch) +- [#30151](https://github.com/apache/superset/pull/30151) refactor(frontend): migrate 6 tests from Enzyme to RTL (@hainenber) +- [#30253](https://github.com/apache/superset/pull/30253) chore(build): remove extraneous prettier step in superset-frontend CI (@hainenber) +- [#30257](https://github.com/apache/superset/pull/30257) build(ci): make linkinator advisory (@rusackas) +- [#30242](https://github.com/apache/superset/pull/30242) build(deps, deps-dev): upgrade major versions for dependencies of `@superset/embedded-sdk` (@hainenber) +- [#30228](https://github.com/apache/superset/pull/30228) build(deps): bump send and express in /superset-frontend (@dependabot[bot]) +- [#30229](https://github.com/apache/superset/pull/30229) build(deps): bump serve-static and express in /superset-frontend (@dependabot[bot]) +- [#30232](https://github.com/apache/superset/pull/30232) refactor(explore): Migrate MetricsControl test suite to RTL (@rtexelm) +- [#30226](https://github.com/apache/superset/pull/30226) build(deps): bump serve-static and express in /superset-websocket/utils/client-ws-app (@dependabot[bot]) +- [#30225](https://github.com/apache/superset/pull/30225) build(deps): bump send and express in /superset-websocket/utils/client-ws-app (@dependabot[bot]) +- [#30091](https://github.com/apache/superset/pull/30091) build(deps): update @babel/runtime requirement from ^7.1.2 to ^7.25.6 in /superset-frontend/packages/superset-ui-core (@dependabot[bot]) +- [#25452](https://github.com/apache/superset/pull/25452) chore(frontend): Spelling (@jsoref) +- [#30103](https://github.com/apache/superset/pull/30103) build(deps-dev): update @babel/types requirement from ^7.25.2 to ^7.25.6 in /superset-frontend/plugins/plugin-chart-pivot-table (@dependabot[bot]) +- [#30199](https://github.com/apache/superset/pull/30199) chore(docs): Removing dead link from INTHEWILD.md (@rusackas) +- [#30101](https://github.com/apache/superset/pull/30101) build(deps-dev): bump @types/react from 18.3.3 to 18.3.5 in /docs (@dependabot[bot]) +- [#30036](https://github.com/apache/superset/pull/30036) build(deps-dev): bump webpack from 5.93.0 to 5.94.0 in /docs (@dependabot[bot]) +- [#30179](https://github.com/apache/superset/pull/30179) build(deps): bump antd from 5.20.0 to 5.20.5 in /docs (@dependabot[bot]) +- [#30166](https://github.com/apache/superset/pull/30166) build(deps): bump @types/node from 20.12.7 to 22.5.4 in /superset-frontend (@dependabot[bot]) +- [#30097](https://github.com/apache/superset/pull/30097) build(deps-dev): bump typescript from 4.9.5 to 5.5.4 in /superset-websocket (@dependabot[bot]) +- [#30088](https://github.com/apache/superset/pull/30088) build(deps): bump core-js from 3.37.1 to 3.38.1 in /superset-frontend/packages/superset-ui-demo (@dependabot[bot]) +- [#29963](https://github.com/apache/superset/pull/29963) build(dev-deps, deps): upgrade major versions for FE deps (@hainenber) +- [#30167](https://github.com/apache/superset/pull/30167) chore(docs): bump docusaurus from 3.4.0 to 3.5.2 (@villebro) +- [#30094](https://github.com/apache/superset/pull/30094) build(deps): bump ws and @types/ws in /superset-websocket (@dependabot[bot]) +- [#30105](https://github.com/apache/superset/pull/30105) build(deps-dev): bump @docusaurus/module-type-aliases from 3.4.0 to 3.5.2 in /docs (@dependabot[bot]) +- [#30111](https://github.com/apache/superset/pull/30111) build(deps): bump react-ultimate-pagination and @types/react-ultimate-pagination in /superset-frontend (@dependabot[bot]) +- [#30106](https://github.com/apache/superset/pull/30106) build(deps): bump prism-react-renderer from 2.3.1 to 2.4.0 in /docs (@dependabot[bot]) +- [#30107](https://github.com/apache/superset/pull/30107) build(deps-dev): bump @docusaurus/tsconfig from 3.4.0 to 3.5.2 in /docs (@dependabot[bot]) +- [#30108](https://github.com/apache/superset/pull/30108) build(deps): bump react-svg-pan-zoom from 3.12.1 to 3.13.1 in /docs (@dependabot[bot]) +- [#30095](https://github.com/apache/superset/pull/30095) build(deps-dev): bump ts-jest from 29.1.5 to 29.2.5 in /superset-websocket (@dependabot[bot]) +- [#30096](https://github.com/apache/superset/pull/30096) build(deps): bump uuid and @types/uuid in /superset-websocket (@dependabot[bot]) +- [#30143](https://github.com/apache/superset/pull/30143) build(deps): bump cryptography from 42.0.7 to 42.0.8 (@dependabot[bot]) +- [#30118](https://github.com/apache/superset/pull/30118) build(deps-dev): bump prettier-plugin-packagejson from 2.4.10 to 2.5.2 in /superset-frontend (@dependabot[bot]) +- [#30127](https://github.com/apache/superset/pull/30127) docs: Fixing missing 'c' in installation guide documentation (@JordanTB) +- [#30155](https://github.com/apache/superset/pull/30155) chore(docs): replace http with https (@villebro) +- [#30072](https://github.com/apache/superset/pull/30072) chore(tests): skip extremely flaky gaq test (@villebro) +- [#30153](https://github.com/apache/superset/pull/30153) chore(docs): update xendit link (@villebro) +- [#30021](https://github.com/apache/superset/pull/30021) chore: accelerate docker compose by skipping frontend build (@mistercrunch) +- [#30090](https://github.com/apache/superset/pull/30090) build(deps): bump aws-actions/amazon-ecs-deploy-task-definition from 1 to 2 (@dependabot[bot]) +- [#30037](https://github.com/apache/superset/pull/30037) build(deps-dev): bump webpack from 5.76.0 to 5.94.0 in /superset-embedded-sdk (@dependabot[bot]) +- [#30038](https://github.com/apache/superset/pull/30038) build(deps-dev): bump webpack from 5.93.0 to 5.94.0 in /superset-frontend (@dependabot[bot]) +- [#30102](https://github.com/apache/superset/pull/30102) build(deps-dev): bump eslint-plugin-react-prefer-function-component from 0.0.7 to 3.3.0 in /superset-frontend (@dependabot[bot]) +- [#30117](https://github.com/apache/superset/pull/30117) build(deps): bump d3-time-format and @types/d3-time-format in /superset-frontend (@dependabot[bot]) +- [#30116](https://github.com/apache/superset/pull/30116) build(deps-dev): bump eslint-plugin-no-only-tests from 2.4.0 to 3.3.0 in /superset-frontend (@dependabot[bot]) +- [#30027](https://github.com/apache/superset/pull/30027) refactor(databases): Create constants.ts, move interface to types.ts (@rtexelm) +- [#30030](https://github.com/apache/superset/pull/30030) chore(docs): docker instructions use `docker compose` instead of the deprecated `docker-compose` (@rusackas) +- [#30057](https://github.com/apache/superset/pull/30057) chore(docs): clean up a few md errors (@villebro) +- [#29586](https://github.com/apache/superset/pull/29586) chore(translations): Arabic translations (@abdilra7eem) +- [#30011](https://github.com/apache/superset/pull/30011) chore(deps): bump core-js (@rusackas) +- [#30007](https://github.com/apache/superset/pull/30007) chore(deps): bump cross-env (@rusackas) +- [#30008](https://github.com/apache/superset/pull/30008) build(deps): bump micromatch from 4.0.4 to 4.0.8 in /superset-frontend/cypress-base (@dependabot[bot]) +- [#30009](https://github.com/apache/superset/pull/30009) build(deps): bump micromatch from 4.0.5 to 4.0.8 in /docs (@dependabot[bot]) +- [#27832](https://github.com/apache/superset/pull/27832) build(deps): bump remark-gfm from 3.0.1 to 4.0.0 in /superset-frontend/packages/superset-ui-core (@dependabot[bot]) +- [#28292](https://github.com/apache/superset/pull/28292) build(deps): bump d3-time from 1.1.0 to 3.1.0 in /superset-frontend/packages/superset-ui-core (@dependabot[bot]) +- [#29990](https://github.com/apache/superset/pull/29990) chore(init): adding link to secret key instructions (@rusackas) +- [#29947](https://github.com/apache/superset/pull/29947) build(deps): bump ws and @applitools/eyes-cypress in /superset-frontend/cypress-base (@dependabot[bot]) +- [#29988](https://github.com/apache/superset/pull/29988) build(node): Bumping to Node 20 (@rusackas) +- [#25454](https://github.com/apache/superset/pull/25454) chore(tests): Spelling (@jsoref) +- [#29970](https://github.com/apache/superset/pull/29970) docs: improve pre-commit docs and discoverability when CI fails (@mistercrunch) +- [#29964](https://github.com/apache/superset/pull/29964) build(deps-dev): bump eslint-plugin-cypress from 2.11.2 to 3.4.0 in /superset-frontend + corresponding refactor (@hainenber) +- [#29969](https://github.com/apache/superset/pull/29969) chore(antd): straightening out button import paths (@rusackas) +- [#29948](https://github.com/apache/superset/pull/29948) chore(deps): bump micromatch (@rusackas) +- [#29952](https://github.com/apache/superset/pull/29952) chore: add additional code owners to migrations (@sadpandajoe) +- [#29945](https://github.com/apache/superset/pull/29945) build(deps): bump axios from 1.6.8 to 1.7.4 in /docs (@dependabot[bot]) +- [#29949](https://github.com/apache/superset/pull/29949) build(deps-dev): bump axios from 1.7.3 to 1.7.4 in /superset-frontend (@dependabot[bot]) +- [#29946](https://github.com/apache/superset/pull/29946) build(deps-dev): bump axios from 1.6.0 to 1.7.4 in /superset-embedded-sdk (@dependabot[bot]) +- [#29904](https://github.com/apache/superset/pull/29904) chore: Changes the migrations owners (@michael-s-molina) +- [#29868](https://github.com/apache/superset/pull/29868) chore: remove useless GitHub action (@mistercrunch) +- [#29869](https://github.com/apache/superset/pull/29869) chore: remove useless GitHub action required check (@mistercrunch) +- [#29859](https://github.com/apache/superset/pull/29859) chore(deps): bumping underscore via npm override (@rusackas) +- [#29876](https://github.com/apache/superset/pull/29876) chore(docs): reorder fs users (@villebro) +- [#29841](https://github.com/apache/superset/pull/29841) chore(deps): bumping jquery (@rusackas) +- [#29870](https://github.com/apache/superset/pull/29870) docs: add unit to companies list (@amitmiran137) +- [#29652](https://github.com/apache/superset/pull/29652) chore(build): uplift several outdated frontend packages (@hainenber) +- [#29866](https://github.com/apache/superset/pull/29866) chore: pre-matrixify pre-commit check (@mistercrunch) +- [#29844](https://github.com/apache/superset/pull/29844) chore(cleanup): Removing bootstrap (experimental) (@rusackas) +- [#29863](https://github.com/apache/superset/pull/29863) chore: describe timezone issue with alerts and reports scheduler in UPDATING.md (@danielli-ziprecruiter) +- [#29855](https://github.com/apache/superset/pull/29855) perf: Lazy load rehype-raw and react-markdown (@kgabryje) +- [#29788](https://github.com/apache/superset/pull/29788) perf: Remove antd-with-locales import (@kgabryje) +- [#29791](https://github.com/apache/superset/pull/29791) perf: Lazy load moment-timezone (@kgabryje) +- [#29808](https://github.com/apache/superset/pull/29808) build(deps-dev): update @babel/types requirement from ^7.24.5 to ^7.25.2 in /superset-frontend/plugins/plugin-chart-pivot-table (@dependabot[bot]) +- [#29838](https://github.com/apache/superset/pull/29838) chore(deps): npm audit fix results (@rusackas) +- [#28294](https://github.com/apache/superset/pull/28294) build(deps): bump react-bootstrap-slider from 2.1.5 to 3.0.0 in /superset-frontend/plugins/legacy-preset-chart-deckgl (@dependabot[bot]) +- [#29756](https://github.com/apache/superset/pull/29756) build(deps): bump react-diff-viewer-continued from 3.2.5 to 3.4.0 in /superset-frontend (@dependabot[bot]) +- [#29759](https://github.com/apache/superset/pull/29759) build(deps-dev): bump eslint-plugin-file-progress from 1.2.0 to 1.4.0 in /superset-frontend (@dependabot[bot]) +- [#29812](https://github.com/apache/superset/pull/29812) build(deps): bump @fontsource/inter from 5.0.19 to 5.0.20 in /superset-frontend (@dependabot[bot]) +- [#29813](https://github.com/apache/superset/pull/29813) build(deps): bump chrono-node from 2.7.5 to 2.7.6 in /superset-frontend (@dependabot[bot]) +- [#29815](https://github.com/apache/superset/pull/29815) build(deps): bump mustache from 2.3.2 to 4.2.0 in /superset-frontend (@dependabot[bot]) +- [#29816](https://github.com/apache/superset/pull/29816) build(deps-dev): bump @types/react-syntax-highlighter from 15.5.11 to 15.5.13 in /superset-frontend (@dependabot[bot]) +- [#29820](https://github.com/apache/superset/pull/29820) build(deps-dev): bump style-loader from 3.3.4 to 4.0.0 in /superset-frontend (@dependabot[bot]) +- [#29821](https://github.com/apache/superset/pull/29821) build(deps): bump memoize-one from 5.1.1 to 5.2.1 in /superset-frontend (@dependabot[bot]) +- [#29809](https://github.com/apache/superset/pull/29809) build(deps-dev): bump @types/jest from 27.0.2 to 29.5.12 in /superset-websocket (@dependabot[bot]) +- [#29811](https://github.com/apache/superset/pull/29811) build(deps-dev): bump @types/node from 22.0.0 to 22.0.2 in /superset-websocket (@dependabot[bot]) +- [#29758](https://github.com/apache/superset/pull/29758) build(deps): bump rimraf from 3.0.2 to 6.0.1 in /superset-frontend (@dependabot[bot]) +- [#29787](https://github.com/apache/superset/pull/29787) perf: Antd icons tree shaking (@kgabryje) +- [#29796](https://github.com/apache/superset/pull/29796) perf: Lazy load React Ace (@kgabryje) +- [#29792](https://github.com/apache/superset/pull/29792) chore: deleting vestigial EMAIL_NOTIFICATIONS (@rusackas) +- [#29673](https://github.com/apache/superset/pull/29673) style: remove uppercase from labels, buttons, tabs to align with design system (@mistercrunch) +- [#29755](https://github.com/apache/superset/pull/29755) build(deps): bump @types/lodash from 4.17.0 to 4.17.7 in /superset-frontend (@dependabot[bot]) +- [#29765](https://github.com/apache/superset/pull/29765) build(deps-dev): bump webpack from 5.89.0 to 5.93.0 in /superset-frontend (@dependabot[bot]) +- [#29794](https://github.com/apache/superset/pull/29794) chore(deps): bump dayjs to unblock CI. (@rusackas) +- [#29790](https://github.com/apache/superset/pull/29790) chore(docs): remove mention of MariaDB in dev environment setup (@sfirke) +- [#29738](https://github.com/apache/superset/pull/29738) build(deps-dev): bump @types/node from 20.13.0 to 22.0.0 in /superset-websocket (@dependabot[bot]) +- [#29748](https://github.com/apache/superset/pull/29748) build(deps): bump @ant-design/icons from 5.3.7 to 5.4.0 in /docs (@dependabot[bot]) +- [#29747](https://github.com/apache/superset/pull/29747) build(deps-dev): bump webpack from 5.92.1 to 5.93.0 in /docs (@dependabot[bot]) +- [#29427](https://github.com/apache/superset/pull/29427) chore(deps): bump abortcontroller-polyfill from 1.2.1 to 1.7.5 in /superset-frontend (@dependabot[bot]) +- [#28820](https://github.com/apache/superset/pull/28820) chore(deps): bump d3-hierarchy from 1.1.9 to 3.1.2 in /superset-frontend (@dependabot[bot]) +- [#29740](https://github.com/apache/superset/pull/29740) build(deps-dev): update @types/lodash requirement from ^4.17.6 to ^4.17.7 in /superset-frontend/plugins/plugin-chart-handlebars (@dependabot[bot]) +- [#29743](https://github.com/apache/superset/pull/29743) build(deps): update underscore requirement from ^1.13.6 to ^1.13.7 in /superset-frontend/plugins/legacy-preset-chart-deckgl (@dependabot[bot]) +- [#29763](https://github.com/apache/superset/pull/29763) build(deps-dev): bump history from 4.10.1 to 5.3.0 in /superset-frontend (@dependabot[bot]) +- [#29760](https://github.com/apache/superset/pull/29760) build(deps-dev): bump ts-loader from 7.0.5 to 9.5.1 in /superset-frontend (@dependabot[bot]) +- [#28297](https://github.com/apache/superset/pull/28297) build(deps-dev): update @babel/types requirement from ^7.24.0 to ^7.24.5 in /superset-frontend/plugins/plugin-chart-pivot-table (@dependabot[bot]) +- [#29767](https://github.com/apache/superset/pull/29767) build(deps): bump fast-xml-parser from 4.2.7 to 4.4.1 in /superset-frontend (@dependabot[bot]) +- [#29739](https://github.com/apache/superset/pull/29739) build(deps): bump debug from 4.3.5 to 4.3.6 in /superset-websocket/utils/client-ws-app (@dependabot[bot]) +- [#29742](https://github.com/apache/superset/pull/29742) build(deps-dev): bump prettier from 3.2.5 to 3.3.3 in /superset-websocket (@dependabot[bot]) +- [#29744](https://github.com/apache/superset/pull/29744) build(deps): bump deck.gl from 9.0.21 to 9.0.24 in /superset-frontend/plugins/legacy-preset-chart-deckgl (@dependabot[bot]) +- [#29746](https://github.com/apache/superset/pull/29746) build(deps): bump @types/lodash from 4.17.4 to 4.17.7 in /superset-websocket (@dependabot[bot]) +- [#29750](https://github.com/apache/superset/pull/29750) build(deps-dev): bump typescript from 5.5.2 to 5.5.4 in /docs (@dependabot[bot]) +- [#29751](https://github.com/apache/superset/pull/29751) build(deps): bump @docsearch/react from 3.6.0 to 3.6.1 in /docs (@dependabot[bot]) +- [#29753](https://github.com/apache/superset/pull/29753) build(deps-dev): bump mini-css-extract-plugin from 2.7.6 to 2.9.0 in /superset-frontend (@dependabot[bot]) +- [#29754](https://github.com/apache/superset/pull/29754) build(deps-dev): bump @svgr/webpack from 8.0.1 to 8.1.0 in /superset-frontend (@dependabot[bot]) +- [#29762](https://github.com/apache/superset/pull/29762) build(deps): bump ace-builds from 1.4.14 to 1.35.4 in /superset-frontend (@dependabot[bot]) +- [#29731](https://github.com/apache/superset/pull/29731) chore(build): pin Storybook-related packages to 8.1.11 as further v8+ version requires React 18 (@hainenber) +- [#26557](https://github.com/apache/superset/pull/26557) build(deps-dev): bump thread-loader from 3.0.4 to 4.0.2 in /superset-frontend (@dependabot[bot]) diff --git a/Dockerfile b/Dockerfile index e3577c67316e..335e50e2f4ca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,16 +18,19 @@ ###################################################################### # Node stage to deal with static asset construction ###################################################################### -ARG PY_VER=3.11-slim-bookworm +ARG PY_VER=3.11.13-slim-bookworm # If BUILDPLATFORM is null, set it to 'amd64' (or leave as is otherwise). ARG BUILDPLATFORM=${BUILDPLATFORM:-amd64} +# Include translations in the final build +ARG BUILD_TRANSLATIONS="false" + ###################################################################### # superset-node-ci used as a base for building frontend assets and CI ###################################################################### -FROM --platform=${BUILDPLATFORM} node:20-bullseye-slim AS superset-node-ci -ARG BUILD_TRANSLATIONS="false" # Include translations in the final build +FROM --platform=${BUILDPLATFORM} node:20-bookworm-slim AS superset-node-ci +ARG BUILD_TRANSLATIONS ENV BUILD_TRANSLATIONS=${BUILD_TRANSLATIONS} ARG DEV_MODE="false" # Skip frontend build in dev mode ENV DEV_MODE=${DEV_MODE} @@ -122,10 +125,13 @@ ENV PATH="/app/.venv/bin:${PATH}" ###################################################################### FROM python-base AS python-translation-compiler +ARG BUILD_TRANSLATIONS +ENV BUILD_TRANSLATIONS=${BUILD_TRANSLATIONS} + # Install Python dependencies using docker/pip-install.sh COPY requirements/translations.txt requirements/ RUN --mount=type=cache,target=/root/.cache/uv \ - /app/docker/pip-install.sh --requires-build-essential -r requirements/translations.txt + . /app/.venv/bin/activate && /app/docker/pip-install.sh --requires-build-essential -r requirements/translations.txt COPY superset/translations/ /app/translations_mo/ RUN if [ "$BUILD_TRANSLATIONS" = "true" ]; then \ diff --git a/RELEASING/Dockerfile.from_local_tarball b/RELEASING/Dockerfile.from_local_tarball index 0a2613c18288..6240439050a7 100644 --- a/RELEASING/Dockerfile.from_local_tarball +++ b/RELEASING/Dockerfile.from_local_tarball @@ -30,12 +30,12 @@ RUN apt-get install -y apt-transport-https apt-utils # Install superset dependencies # https://superset.apache.org/docs/installation/installing-superset-from-scratch RUN apt-get install -y build-essential libssl-dev \ - libffi-dev python3-dev libsasl2-dev libldap2-dev libxi-dev chromium + libffi-dev python3-dev libsasl2-dev libldap2-dev libxi-dev chromium zstd # Install nodejs for custom build # https://nodejs.org/en/download/package-manager/ RUN set -eux; \ - curl -sL https://deb.nodesource.com/setup_18.x | bash -; \ + curl -sL https://deb.nodesource.com/setup_20.x | bash -; \ apt-get install -y nodejs; \ node --version; RUN if ! which npm; then apt-get install -y npm; fi @@ -64,7 +64,7 @@ RUN pip install --upgrade setuptools pip \ RUN flask fab babel-compile --target superset/translations ENV PATH=/home/superset/superset/bin:$PATH \ - PYTHONPATH=/home/superset/superset/:$PYTHONPATH \ + PYTHONPATH=/home/superset/superset/ \ SUPERSET_TESTENV=true COPY from_tarball_entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] diff --git a/RELEASING/Dockerfile.from_svn_tarball b/RELEASING/Dockerfile.from_svn_tarball index 22883552cabf..f14754c6901d 100644 --- a/RELEASING/Dockerfile.from_svn_tarball +++ b/RELEASING/Dockerfile.from_svn_tarball @@ -29,13 +29,16 @@ RUN apt-get install -y apt-transport-https apt-utils # Install superset dependencies # https://superset.apache.org/docs/installation/installing-superset-from-scratch -RUN apt-get install -y build-essential libssl-dev \ - libffi-dev python3-dev libsasl2-dev libldap2-dev libxi-dev chromium +RUN apt-get install -y subversion build-essential libssl-dev \ + libffi-dev python3-dev libsasl2-dev libldap2-dev libxi-dev chromium zstd # Install nodejs for custom build # https://nodejs.org/en/download/package-manager/ -RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - \ - && apt-get install -y nodejs +RUN set -eux; \ + curl -sL https://deb.nodesource.com/setup_20.x | bash -; \ + apt-get install -y nodejs; \ + node --version; +RUN if ! which npm; then apt-get install -y npm; fi RUN mkdir -p /home/superset RUN chown superset /home/superset @@ -46,14 +49,12 @@ ARG VERSION # Can fetch source from svn or copy tarball from local mounted directory RUN svn co https://dist.apache.org/repos/dist/dev/superset/$VERSION ./ RUN tar -xvf *.tar.gz -WORKDIR apache-superset-$VERSION +WORKDIR /home/superset/apache-superset-$VERSION/superset-frontend -RUN cd superset-frontend \ - && npm ci \ +RUN npm ci \ && npm run build \ && rm -rf node_modules - WORKDIR /home/superset/apache-superset-$VERSION RUN pip install --upgrade setuptools pip \ && pip install -r requirements/base.txt \ @@ -62,6 +63,6 @@ RUN pip install --upgrade setuptools pip \ RUN flask fab babel-compile --target superset/translations ENV PATH=/home/superset/superset/bin:$PATH \ - PYTHONPATH=/home/superset/superset/:$PYTHONPATH + PYTHONPATH=/home/superset/superset/ COPY from_tarball_entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] diff --git a/RELEASING/README.md b/RELEASING/README.md index eb024762a911..e7d021ce09b3 100644 --- a/RELEASING/README.md +++ b/RELEASING/README.md @@ -452,7 +452,7 @@ cd ../ # Compile translations for the backend -./scripts/translations/generate_po_files.sh +./scripts/translations/generate_mo_files.sh # build the python distribution python -m build diff --git a/UPDATING.md b/UPDATING.md index 9216650bafb1..a7d2ebf053cd 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -22,10 +22,11 @@ under the License. This file documents any backwards-incompatible changes in Superset and assists people when migrating to a new version. -## Next +## 5.0.0 +- [31976](https://github.com/apache/superset/pull/31976) Removed the `DISABLE_LEGACY_DATASOURCE_EDITOR` feature flag. The previous value of the feature flag was `True` and now the feature is permanently removed. - [31959](https://github.com/apache/superset/pull/32000) Removes CSV_UPLOAD_MAX_SIZE config, use your web server to control file upload size. -- [31959](https://github.com/apache/superset/pull/31959) Removes the following endpoints from data uploads: /api/v1/database//_upload and /api/v1/database/_metadata, in favour of new one (Details on the PR). And simplifies permissions. +- [31959](https://github.com/apache/superset/pull/31959) Removes the following endpoints from data uploads: `/api/v1/database//_upload` and `/api/v1/database/_metadata`, in favour of new one (Details on the PR). And simplifies permissions. - [31844](https://github.com/apache/superset/pull/31844) The `ALERT_REPORTS_EXECUTE_AS` and `THUMBNAILS_EXECUTE_AS` config parameters have been renamed to `ALERT_REPORTS_EXECUTORS` and `THUMBNAILS_EXECUTORS` respectively. A new config flag `CACHE_WARMUP_EXECUTORS` has also been introduced to be able to control which user is used to execute cache warmup tasks. Finally, the config flag `THUMBNAILS_SELENIUM_USER` has been removed. To use a fixed executor for async tasks, use the new `FixedExecutor` class. See the config and docs for more info on setting up different executor profiles. - [31894](https://github.com/apache/superset/pull/31894) Domain sharding is deprecated in favor of HTTP2. The `SUPERSET_WEBSERVER_DOMAINS` configuration will be removed in the next major version (6.0) - [31794](https://github.com/apache/superset/pull/31794) Removed the previously deprecated `DASHBOARD_CROSS_FILTERS` feature flag @@ -45,9 +46,7 @@ assists people when migrating to a new version. - [25166](https://github.com/apache/superset/pull/25166) Changed the default configuration of `UPLOAD_FOLDER` from `/app/static/uploads/` to `/static/uploads/`. It also removed the unused `IMG_UPLOAD_FOLDER` and `IMG_UPLOAD_URL` configuration options. - [30284](https://github.com/apache/superset/pull/30284) Deprecated GLOBAL_ASYNC_QUERIES_REDIS_CONFIG in favor of the new GLOBAL_ASYNC_QUERIES_CACHE_BACKEND configuration. To leverage Redis Sentinel, set CACHE_TYPE to RedisSentinelCache, or use RedisCache for standalone Redis - [31961](https://github.com/apache/superset/pull/31961) Upgraded React from version 16.13.1 to 17.0.2. If you are using custom frontend extensions or plugins, you may need to update them to be compatible with React 17. - - -### Potential Downtime +- [31260](https://github.com/apache/superset/pull/31260) Docker images now use `uv pip install` instead of `pip install` to manage the python envrionment. Most docker-based deployments will be affected, whether you derive one of the published images, or have custom bootstrap script that install python libraries (drivers) ## 4.1.0 diff --git a/docker/docker-bootstrap.sh b/docker/docker-bootstrap.sh index ebaec6c963db..fd017622a133 100755 --- a/docker/docker-bootstrap.sh +++ b/docker/docker-bootstrap.sh @@ -50,7 +50,11 @@ fi # if [ -f "${REQUIREMENTS_LOCAL}" ]; then echo "Installing local overrides at ${REQUIREMENTS_LOCAL}" - uv pip install --no-cache-dir -r "${REQUIREMENTS_LOCAL}" + if command -v uv > /dev/null 2>&1; then + uv pip install --no-cache-dir -r "${REQUIREMENTS_LOCAL}" + else + pip install --no-cache-dir -r "${REQUIREMENTS_LOCAL}" + fi else echo "Skipping local overrides" fi diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf index eda47ef580d7..e60a07fbdf8c 100644 --- a/docker/nginx/nginx.conf +++ b/docker/nginx/nginx.conf @@ -112,6 +112,12 @@ http { proxy_set_header Host $host; } + location /static { + proxy_pass http://host.docker.internal:9000; # Proxy to superset-node + proxy_http_version 1.1; + proxy_set_header Host $host; + } + location / { proxy_pass http://superset_app; proxy_set_header Host $host; diff --git a/docs/docs/installation/kubernetes.mdx b/docs/docs/installation/kubernetes.mdx index 6cb2096584fe..9d79f7f2b248 100644 --- a/docs/docs/installation/kubernetes.mdx +++ b/docs/docs/installation/kubernetes.mdx @@ -1,16 +1,17 @@ --- -title: Kubernetes +title: Kubernetes hide_title: true sidebar_position: 2 version: 1 --- -import useBaseUrl from "@docusaurus/useBaseUrl"; +import useBaseUrl from '@docusaurus/useBaseUrl'; # Installing on Kubernetes - -

+ +
+
Running Superset on Kubernetes is supported with the provided [Helm](https://helm.sh/) chart found in the official [Superset helm repository](https://apache.github.io/superset/index.yaml). @@ -134,7 +135,7 @@ init: ``` :::note -Superset uses [Scarf Gateway](https://about.scarf.sh/scarf-gateway) to collect telemetry data. Knowing the installation counts for different Superset versions informs the project's decisions about patching and long-term support. Scarf purges personally identifiable information (PII) and provides only aggregated statistics. +Superset uses [Scarf Gateway](https://about.scarf.sh/scarf-gateway) to collect telemetry data. Knowing the installation counts for different Superset versions informs the project's decisions about patching and long-term support. Scarf purges personally identifiable information (PII) and provides only aggregated statistics. To opt-out of this data collection in your Helm-based installation, edit the `repository:` line in your `helm/superset/values.yaml` file, replacing `apachesuperset.docker.scarf.sh/apache/superset` with `apache/superset` to pull the image directly from Docker Hub. ::: @@ -154,10 +155,11 @@ See [Install Database Drivers](/docs/configuration/databases) for more informati ::: The following example installs the drivers for BigQuery and Elasticsearch, allowing you to connect to these data sources within your Superset setup: + ```yaml bootstrapScript: | #!/bin/bash - pip install psycopg2==2.9.6 \ + uv pip install psycopg2==2.9.6 \ sqlalchemy-bigquery==1.6.1 \ elasticsearch-dbapi==0.2.5 &&\ if [ ! -f ~/bootstrap ]; then echo "Running Superset with uid {{ .Values.runAsUser }}" > ~/bootstrap; fi @@ -191,7 +193,7 @@ Those can be passed as key/values either with `extraEnv` or `extraSecretEnv` if extraEnv: SMTP_HOST: smtp.gmail.com SMTP_USER: user@gmail.com - SMTP_PORT: "587" + SMTP_PORT: '587' SMTP_MAIL_FROM: user@gmail.com extraSecretEnv: @@ -352,7 +354,7 @@ supersetCeleryBeat: extraEnv: SMTP_HOST: smtp.gmail.com SMTP_USER: user@gmail.com - SMTP_PORT: "587" + SMTP_PORT: '587' SMTP_MAIL_FROM: user@gmail.com extraSecretEnv: diff --git a/docs/docs/security/security.mdx b/docs/docs/security/security.mdx index b2e805c2b57e..839b5afa9413 100644 --- a/docs/docs/security/security.mdx +++ b/docs/docs/security/security.mdx @@ -281,6 +281,49 @@ TALISMAN_CONFIG = { "content_security_policy": { ... ``` +#### Configuring Talisman in Superset + +Talisman settings in Superset can be modified using superset_config.py. If you need to adjust security policies, you can override the default configuration. + +Example: Overriding Talisman Configuration in superset_config.py for loading images form s3 or other external sources. + +```python +TALISMAN_CONFIG = { + "content_security_policy": { + "base-uri": ["'self'"], + "default-src": ["'self'"], + "img-src": [ + "'self'", + "blob:", + "data:", + "https://apachesuperset.gateway.scarf.sh", + "https://static.scarf.sh/", + # "https://cdn.brandfolder.io", # Uncomment when SLACK_ENABLE_AVATARS is True # noqa: E501 + "ows.terrestris.de", + "aws.s3.com", # Add Your Bucket or external data source + ], + "worker-src": ["'self'", "blob:"], + "connect-src": [ + "'self'", + "https://api.mapbox.com", + "https://events.mapbox.com", + ], + "object-src": "'none'", + "style-src": [ + "'self'", + "'unsafe-inline'", + ], + "script-src": ["'self'", "'strict-dynamic'"], + }, + "content_security_policy_nonce_in": ["script-src"], + "force_https": False, + "session_cookie_secure": False, +} +``` + +# For more information on setting up Talisman, please refer to +https://superset.apache.org/docs/configuration/networking-settings/#changing-flask-talisman-csp + ### Reporting Security Vulnerabilities Apache Software Foundation takes a rigorous standpoint in annihilating the security issues in its diff --git a/docs/src/resources/data.js b/docs/src/resources/data.js index baeed74eb954..7bd418778ee8 100644 --- a/docs/src/resources/data.js +++ b/docs/src/resources/data.js @@ -137,4 +137,9 @@ export const Databases = [ href: 'https://www.denodo.com/', imgName: 'denodo.png', }, + { + title: 'TDengine', + href: 'https://www.tdengine.com/', + imgName: 'tdengine.png', + }, ]; diff --git a/docs/static/img/databases/tdengine.png b/docs/static/img/databases/tdengine.png new file mode 100644 index 000000000000..cc9d1852e00b Binary files /dev/null and b/docs/static/img/databases/tdengine.png differ diff --git a/pyproject.toml b/pyproject.toml index 0d93f9e96f36..6229f283c3d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ dependencies = [ "cryptography>=42.0.4, <44.0.0", "deprecation>=2.1.0, <2.2.0", "flask>=2.2.5, <3.0.0", - "flask-appbuilder>=4.5.3, <5.0.0", + "flask-appbuilder>=4.5.5, <5.0.0", "flask-caching>=2.1.0, <3", "flask-compress>=1.13, <2.0", "flask-talisman>=1.0.0, <2.0", diff --git a/requirements/base.txt b/requirements/base.txt index 3ba7e3f98454..bb07e1905c8b 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -108,7 +108,7 @@ flask==2.3.3 # flask-session # flask-sqlalchemy # flask-wtf -flask-appbuilder==4.5.3 +flask-appbuilder==4.5.5 # via apache-superset (pyproject.toml) flask-babel==2.0.0 # via flask-appbuilder @@ -148,9 +148,10 @@ greenlet==3.0.3 # via # apache-superset (pyproject.toml) # shillelagh + # sqlalchemy gunicorn==23.0.0 # via apache-superset (pyproject.toml) -h11==0.14.0 +h11==0.16.0 # via wsproto hashids==1.3.1 # via apache-superset (pyproject.toml) @@ -223,7 +224,6 @@ numpy==1.26.4 # bottleneck # numexpr # pandas - # pyarrow odfpy==1.4.1 # via pandas openpyxl==3.1.5 @@ -358,7 +358,7 @@ sqlalchemy-utils==0.38.3 # via # apache-superset (pyproject.toml) # flask-appbuilder -sqlglot==26.1.3 +sqlglot==26.11.1 # via apache-superset (pyproject.toml) sqlparse==0.5.2 # via apache-superset (pyproject.toml) diff --git a/requirements/development.txt b/requirements/development.txt index 276ca4e20e7c..c325e1e8708a 100644 --- a/requirements/development.txt +++ b/requirements/development.txt @@ -190,7 +190,7 @@ flask==2.3.3 # flask-sqlalchemy # flask-testing # flask-wtf -flask-appbuilder==4.5.3 +flask-appbuilder==4.5.5 # via # -c requirements/base.txt # apache-superset @@ -306,6 +306,7 @@ greenlet==3.0.3 # apache-superset # gevent # shillelagh + # sqlalchemy grpcio==1.68.0 # via # apache-superset @@ -317,7 +318,7 @@ gunicorn==23.0.0 # via # -c requirements/base.txt # apache-superset -h11==0.14.0 +h11==0.16.0 # via # -c requirements/base.txt # wsproto @@ -459,7 +460,6 @@ numpy==1.26.4 # pandas # pandas-gbq # prophet - # pyarrow oauthlib==3.2.2 # via requests-oauthlib odfpy==1.4.1 @@ -784,7 +784,7 @@ sqlalchemy-utils==0.38.3 # -c requirements/base.txt # apache-superset # flask-appbuilder -sqlglot==26.1.3 +sqlglot==26.11.1 # via # -c requirements/base.txt # apache-superset diff --git a/scripts/change_detector.py b/scripts/change_detector.py index 7efd0ede5ec8..7d897fb3cf67 100755 --- a/scripts/change_detector.py +++ b/scripts/change_detector.py @@ -134,7 +134,7 @@ def main(event_type: str, sha: str, repo: str) -> None: with open(output_path, "a") as f: for check, changed in changes_detected.items(): if changed: - print(f"{check}={str(changed).lower()}", file=f) + print(f"{check}=true", file=f) print(f"Triggering group: {check}") diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/drillby.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/drillby.test.ts index e471d1da8caa..44d994b4f494 100644 --- a/superset-frontend/cypress-base/cypress/e2e/dashboard/drillby.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/drillby.test.ts @@ -511,29 +511,29 @@ describe('Drill by modal', () => { it('Line chart', () => { testEchart('echarts_timeseries_line', 'Line Chart', [ - [70, 93], - [70, 93], + [85, 93], + [85, 93], ]); }); it('Area Chart', () => { testEchart('echarts_area', 'Area Chart', [ - [70, 93], - [70, 93], + [85, 93], + [85, 93], ]); }); it('Scatter Chart', () => { testEchart('echarts_timeseries_scatter', 'Scatter Chart', [ - [70, 93], - [70, 93], + [85, 93], + [85, 93], ]); }); it('Bar Chart', () => { testEchart('echarts_timeseries_bar', 'Bar Chart', [ - [70, 94], - [362, 68], + [85, 94], + [490, 68], ]); }); @@ -566,22 +566,22 @@ describe('Drill by modal', () => { it('Generic Chart', () => { testEchart('echarts_timeseries', 'Generic Chart', [ - [70, 93], - [70, 93], + [85, 93], + [85, 93], ]); }); it('Smooth Line Chart', () => { testEchart('echarts_timeseries_smooth', 'Smooth Line Chart', [ - [70, 93], - [70, 93], + [85, 93], + [85, 93], ]); }); it('Step Line Chart', () => { testEchart('echarts_timeseries_step', 'Step Line Chart', [ - [70, 93], - [70, 93], + [85, 93], + [85, 93], ]); }); @@ -617,8 +617,8 @@ describe('Drill by modal', () => { cy.get('[data-test-viz-type="mixed_timeseries"] canvas').then($canvas => { // click 'boy' cy.wrap($canvas).scrollIntoView(); - cy.wrap($canvas).trigger('mouseover', 70, 93); - cy.wrap($canvas).rightclick(70, 93); + cy.wrap($canvas).trigger('mouseover', 85, 93); + cy.wrap($canvas).rightclick(85, 93); drillBy('name').then(intercepted => { const { queries } = intercepted.request.body; @@ -651,8 +651,8 @@ describe('Drill by modal', () => { cy.get(`[data-test="drill-by-chart"] canvas`).then($canvas => { // click second query cy.wrap($canvas).scrollIntoView(); - cy.wrap($canvas).trigger('mouseover', 246, 114); - cy.wrap($canvas).rightclick(246, 114); + cy.wrap($canvas).trigger('mouseover', 261, 114); + cy.wrap($canvas).rightclick(261, 114); drillBy('ds').then(intercepted => { const { queries } = intercepted.request.body; diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/drilltodetail.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/drilltodetail.test.ts index 4ebd64dd6e50..6183170d3bc0 100644 --- a/superset-frontend/cypress-base/cypress/e2e/dashboard/drilltodetail.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/drilltodetail.test.ts @@ -96,24 +96,24 @@ function testTimeChart(vizType: string) { cy.get(`[data-test-viz-type='${vizType}'] canvas`).then($canvas => { cy.wrap($canvas).scrollIntoView(); - cy.wrap($canvas).trigger('mousemove', 70, 93); - cy.wrap($canvas).rightclick(70, 93); + cy.wrap($canvas).trigger('mousemove', 85, 93); + cy.wrap($canvas).rightclick(85, 93); drillToDetailBy('Drill to detail by 1965'); cy.getBySel('filter-val').should('contain', '1965'); closeModal(); cy.wrap($canvas).scrollIntoView(); - cy.wrap($canvas).trigger('mousemove', 70, 93); - cy.wrap($canvas).rightclick(70, 93); + cy.wrap($canvas).trigger('mousemove', 85, 93); + cy.wrap($canvas).rightclick(85, 93); drillToDetailBy('Drill to detail by boy'); cy.getBySel('filter-val').should('contain', 'boy'); closeModal(); cy.wrap($canvas).scrollIntoView(); - cy.wrap($canvas).trigger('mousemove', 70, 93); - cy.wrap($canvas).rightclick(70, 93); + cy.wrap($canvas).trigger('mousemove', 85, 93); + cy.wrap($canvas).rightclick(85, 93); drillToDetailBy('Drill to detail by all'); cy.getBySel('filter-val').first().should('contain', '1965'); @@ -435,7 +435,7 @@ describe('Drill to detail modal', () => { SUPPORTED_TIER2_CHARTS.forEach(waitForChartLoad); }); - describe('Modal actions', () => { + describe.only('Modal actions', () => { it('clears filters', () => { interceptSamples(); @@ -443,7 +443,7 @@ describe('Drill to detail modal', () => { cy.get("[data-test-viz-type='box_plot'] canvas").then($canvas => { const canvasWidth = $canvas.width() || 0; const canvasHeight = $canvas.height() || 0; - const canvasCenterX = canvasWidth / 3; + const canvasCenterX = canvasWidth / 3 + 15; const canvasCenterY = (canvasHeight * 5) / 6; cy.wrap($canvas).scrollIntoView(); diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/control.test.ts b/superset-frontend/cypress-base/cypress/e2e/explore/control.test.ts index b68d828ba86b..c792a310ef85 100644 --- a/superset-frontend/cypress-base/cypress/e2e/explore/control.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/explore/control.test.ts @@ -51,8 +51,8 @@ describe('Datasource control', () => { ) .first() .focus(); - cy.focused().clear(); - cy.focused().type(`${newMetricName}{enter}`); + cy.focused().clear({ force: true }); + cy.focused().type(`${newMetricName}{enter}`, { force: true }); cy.get('[data-test="datasource-modal-save"]').click(); cy.get('.antd5-modal-confirm-btns button').contains('OK').click(); diff --git a/superset-frontend/jest.config.js b/superset-frontend/jest.config.js index 07553d889e23..13d07aa1989d 100644 --- a/superset-frontend/jest.config.js +++ b/superset-frontend/jest.config.js @@ -75,4 +75,5 @@ module.exports = { }, ], ], + testTimeout: 10000, }; diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 75f9f39d16e8..7d7c94806330 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "superset", - "version": "0.0.0-dev", + "version": "5.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "superset", - "version": "0.0.0-dev", + "version": "5.0.0", "license": "Apache-2.0", "workspaces": [ "packages/*", @@ -3229,9 +3229,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", - "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -17561,9 +17561,9 @@ "license": "CC-BY-4.0" }, "node_modules/canvg": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", - "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", "license": "MIT", "optional": true, "dependencies": { @@ -20981,11 +20981,14 @@ } }, "node_modules/dompurify": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz", - "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz", + "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==", "license": "(MPL-2.0 OR Apache-2.0)", - "optional": true + "optional": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } }, "node_modules/domutils": { "version": "3.2.2", @@ -32660,20 +32663,20 @@ } }, "node_modules/jspdf": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz", - "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.1.tgz", + "integrity": "sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.2", + "@babel/runtime": "^7.26.7", "atob": "^2.1.2", "btoa": "^1.2.1", "fflate": "^0.8.1" }, "optionalDependencies": { - "canvg": "^3.0.6", + "canvg": "^3.0.11", "core-js": "^3.6.0", - "dompurify": "^2.5.4", + "dompurify": "^3.2.4", "html2canvas": "^1.0.0-rc.5" } }, @@ -34399,6 +34402,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -34415,6 +34419,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -34427,6 +34432,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -34451,12 +34457,14 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, "license": "MIT" }, "node_modules/mdast-util-gfm": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", + "dev": true, "license": "MIT", "dependencies": { "mdast-util-from-markdown": "^2.0.0", @@ -34476,6 +34484,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -34493,6 +34502,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -34510,6 +34520,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -34525,6 +34536,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -34542,6 +34554,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -34558,6 +34571,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -34651,6 +34665,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -34672,12 +34687,14 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, "license": "MIT" }, "node_modules/mdast-util-to-markdown/node_modules/unist-util-visit": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -34693,6 +34710,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0" @@ -35196,6 +35214,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35231,6 +35250,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35265,6 +35285,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "dev": true, "license": "MIT", "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", @@ -35285,6 +35306,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", @@ -35301,6 +35323,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -35321,6 +35344,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "dev": true, "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -35339,6 +35363,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dev": true, "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -35356,6 +35381,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "dev": true, "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" @@ -35369,6 +35395,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "dev": true, "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -35386,6 +35413,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35407,6 +35435,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35429,6 +35458,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35449,6 +35479,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35471,6 +35502,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35513,6 +35545,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35532,6 +35565,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35553,6 +35587,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35573,6 +35608,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35592,6 +35628,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35630,6 +35667,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35646,6 +35684,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35665,6 +35704,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -35705,6 +35745,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.4.tgz", "integrity": "sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -43327,6 +43368,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", + "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -43345,12 +43387,14 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, "license": "MIT" }, "node_modules/remark-gfm/node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -43363,6 +43407,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -43379,6 +43424,7 @@ "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -43398,6 +43444,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -43412,6 +43459,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -44089,6 +44137,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -44104,12 +44153,14 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, "license": "MIT" }, "node_modules/remark-stringify/node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -44122,6 +44173,7 @@ "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -44141,6 +44193,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -44155,6 +44208,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -52344,7 +52398,7 @@ "react-markdown": "^8.0.7", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", - "remark-gfm": "^4.0.0", + "remark-gfm": "^3.0.1", "reselect": "^4.0.0", "rison": "^0.1.1", "seedrandom": "^3.0.5", @@ -52388,6 +52442,15 @@ "tinycolor2": "*" } }, + "packages/superset-ui-core/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, "packages/superset-ui-core/node_modules/d3-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", @@ -52434,6 +52497,18 @@ "d3-time": "1 - 2" } }, + "packages/superset-ui-core/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/superset-ui-core/node_modules/fetch-mock": { "version": "11.1.5", "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-11.1.5.tgz", @@ -52456,6 +52531,806 @@ } } }, + "packages/superset-ui-core/node_modules/mdast-util-find-and-replace": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz", + "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/mdast-util-gfm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz", + "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-gfm-autolink-literal": "^1.0.0", + "mdast-util-gfm-footnote": "^1.0.0", + "mdast-util-gfm-strikethrough": "^1.0.0", + "mdast-util-gfm-table": "^1.0.0", + "mdast-util-gfm-task-list-item": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/mdast-util-gfm-autolink-literal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "ccount": "^2.0.0", + "mdast-util-find-and-replace": "^2.0.0", + "micromark-util-character": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/mdast-util-gfm-footnote": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz", + "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0", + "micromark-util-normalize-identifier": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/mdast-util-gfm-strikethrough": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz", + "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/mdast-util-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz", + "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/mdast-util-gfm-task-list-item": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz", + "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/mdast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/mdast-util-to-markdown": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^3.0.0", + "mdast-util-to-string": "^3.0.0", + "micromark-util-decode-string": "^1.0.0", + "unist-util-visit": "^4.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-extension-gfm": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz", + "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^1.0.0", + "micromark-extension-gfm-footnote": "^1.0.0", + "micromark-extension-gfm-strikethrough": "^1.0.0", + "micromark-extension-gfm-table": "^1.0.0", + "micromark-extension-gfm-tagfilter": "^1.0.0", + "micromark-extension-gfm-task-list-item": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/micromark-extension-gfm-autolink-literal": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz", + "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/micromark-extension-gfm-footnote": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz", + "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==", + "license": "MIT", + "dependencies": { + "micromark-core-commonmark": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/micromark-extension-gfm-strikethrough": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz", + "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==", + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/micromark-extension-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz", + "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==", + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/micromark-extension-gfm-tagfilter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz", + "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/micromark-extension-gfm-task-list-item": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz", + "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==", + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "packages/superset-ui-core/node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "packages/superset-ui-core/node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "packages/superset-ui-core/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "packages/superset-ui-core/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "packages/superset-ui-core/node_modules/remark-gfm": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", + "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-gfm": "^2.0.0", + "micromark-extension-gfm": "^2.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/superset-ui-core/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "packages/superset-ui-demo": { "name": "@superset-ui/demo", "version": "0.20.0", @@ -53444,6 +54319,7 @@ "version": "0.20.3", "license": "Apache-2.0", "dependencies": { + "@types/react-redux": "^7.1.10", "d3-array": "^1.2.0", "dayjs": "^1.11.13", "lodash": "^4.17.21" diff --git a/superset-frontend/package.json b/superset-frontend/package.json index ca423f246555..f9d2f9dd2fc6 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -1,6 +1,6 @@ { "name": "superset", - "version": "0.0.0-dev", + "version": "5.0.0", "description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.", "keywords": [ "big", @@ -381,6 +381,7 @@ }, "puppeteer": "^22.4.1", "underscore": "^1.13.7", + "jspdf": "^3.0.1", "fast-glob": { "micromatch": "^4.0.6" } diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/components/labelUtils.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/components/labelUtils.tsx index 03af5c13e869..4ef491f13ce0 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/components/labelUtils.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/components/labelUtils.tsx @@ -23,21 +23,18 @@ import { ColumnMeta, Metric } from '@superset-ui/chart-controls'; const TooltipSectionWrapper = styled.div` ${({ theme }) => css` - display: flex; - flex-direction: column; + display: -webkit-box; + -webkit-line-clamp: 40; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + font-size: ${theme.typography.sizes.s}px; line-height: 1.2; &:not(:last-of-type) { margin-bottom: ${theme.gridUnit * 2}px; } - &:last-of-type { - display: -webkit-box; - -webkit-line-clamp: 40; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - } `} `; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/operators/renameOperator.ts b/superset-frontend/packages/superset-ui-chart-controls/src/operators/renameOperator.ts index 04f3b7ac327f..d6f496c5993c 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/operators/renameOperator.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/operators/renameOperator.ts @@ -26,6 +26,7 @@ import { } from '@superset-ui/core'; import { PostProcessingFactory } from './types'; import { getMetricOffsetsMap, isTimeComparison } from './utils'; +import { TIME_COMPARISON_SEPARATOR } from './utils/constants'; export const renameOperator: PostProcessingFactory = ( formData, @@ -37,50 +38,60 @@ export const renameOperator: PostProcessingFactory = ( ); const { truncate_metric } = formData; const xAxisLabel = getXAxisLabel(formData); + const isTimeComparisonValue = isTimeComparison(formData, queryObject); + // remove or rename top level of column name(metric name) in the MultiIndex when - // 1) only 1 metric + // 1) at least 1 metric // 2) dimension exist // 3) xAxis exist - // 4) time comparison exist, and comparison type is "actual values" - // 5) truncate_metric in form_data and truncate_metric is true + // 4) truncate_metric in form_data and truncate_metric is true if ( - metrics.length === 1 && + metrics.length > 0 && columns.length > 0 && xAxisLabel && - !( - // todo: we should provide an approach to handle derived metrics - ( - isTimeComparison(formData, queryObject) && - [ - ComparisonType.Difference, - ComparisonType.Ratio, - ComparisonType.Percentage, - ].includes(formData.comparison_type) - ) - ) && truncate_metric !== undefined && !!truncate_metric ) { const renamePairs: [string, string | null][] = []; - if ( // "actual values" will add derived metric. // we will rename the "metric" from the metricWithOffset label // for example: "count__1 year ago" => "1 year ago" - isTimeComparison(formData, queryObject) && - formData.comparison_type === ComparisonType.Values + isTimeComparisonValue ) { const metricOffsetMap = getMetricOffsetsMap(formData, queryObject); const timeOffsets = ensureIsArray(formData.time_compare); - [...metricOffsetMap.keys()].forEach(metricWithOffset => { - const offsetLabel = timeOffsets.find(offset => - metricWithOffset.includes(offset), - ); - renamePairs.push([metricWithOffset, offsetLabel]); - }); + [...metricOffsetMap.entries()].forEach( + ([metricWithOffset, metricOnly]) => { + const offsetLabel = timeOffsets.find(offset => + metricWithOffset.includes(offset), + ); + renamePairs.push([ + formData.comparison_type === ComparisonType.Values + ? metricWithOffset + : [formData.comparison_type, metricOnly, metricWithOffset].join( + TIME_COMPARISON_SEPARATOR, + ), + metrics.length > 1 ? `${metricOnly}, ${offsetLabel}` : offsetLabel, + ]); + }, + ); } - renamePairs.push([getMetricLabel(metrics[0]), null]); + if ( + ![ + ComparisonType.Difference, + ComparisonType.Percentage, + ComparisonType.Ratio, + ].includes(formData.comparison_type) && + metrics.length === 1 + ) { + renamePairs.push([getMetricLabel(metrics[0]), null]); + } + + if (renamePairs.length === 0) { + return undefined; + } return { operation: 'rename', diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/sections/chartTitle.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/sections/chartTitle.tsx index e8cda4991fd1..43ad46ba7f71 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/sections/chartTitle.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/sections/chartTitle.tsx @@ -54,7 +54,7 @@ export const titleControls: ControlPanelSectionConfig = { type: 'SelectControl', freeForm: true, clearable: true, - label: t('X AXIS TITLE BOTTOM MARGIN'), + label: t('X Axis Title Margin'), renderTrigger: true, default: TITLE_MARGIN_OPTIONS[0], choices: formatSelectOptions(TITLE_MARGIN_OPTIONS), @@ -84,7 +84,7 @@ export const titleControls: ControlPanelSectionConfig = { clearable: true, label: t('Y Axis Title Margin'), renderTrigger: true, - default: TITLE_MARGIN_OPTIONS[0], + default: TITLE_MARGIN_OPTIONS[1], choices: formatSelectOptions(TITLE_MARGIN_OPTIONS), description: t('Changing this control takes effect instantly'), }, diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts index 09667004a565..62ac94468857 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts @@ -84,6 +84,12 @@ export interface Dataset { filter_select?: boolean; filter_select_enabled?: boolean; column_names?: string[]; + catalog?: string; + schema?: string; + table_name?: string; + database?: Record; + normalize_columns?: boolean; + always_filter_main_dttm?: boolean; } export interface ControlPanelState { @@ -515,6 +521,13 @@ export enum SortSeriesType { Avg = 'avg', } +export type LegendPaddingType = { + top?: number; + bottom?: number; + left?: number; + right?: number; +}; + export type SortSeriesData = { sort_series_type: SortSeriesType; sort_series_ascending: boolean; diff --git a/superset-frontend/packages/superset-ui-chart-controls/test/operators/renameOperator.test.ts b/superset-frontend/packages/superset-ui-chart-controls/test/operators/renameOperator.test.ts index c6b899d5134b..c3a7faacff36 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/test/operators/renameOperator.test.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/test/operators/renameOperator.test.ts @@ -43,12 +43,12 @@ const queryObject: QueryObject = { post_processing: [], }; -test('should skip renameOperator if exists multiple metrics', () => { +test('should skip renameOperator for empty metrics', () => { expect( renameOperator(formData, { ...queryObject, ...{ - metrics: ['count(*)', 'sum(sales)'], + metrics: [], }, }), ).toEqual(undefined); @@ -77,7 +77,23 @@ test('should skip renameOperator if does not exist x_axis and is_timeseries', () ).toEqual(undefined); }); -test('should skip renameOperator if exists derived metrics', () => { +test('should skip renameOperator if not is_timeseries and multi metrics', () => { + expect( + renameOperator(formData, { + ...queryObject, + ...{ is_timeseries: false, metrics: ['count(*)', 'sum(val)'] }, + }), + ).toEqual(undefined); +}); + +test('should add renameOperator', () => { + expect(renameOperator(formData, queryObject)).toEqual({ + operation: 'rename', + options: { columns: { 'count(*)': null }, inplace: true, level: 0 }, + }); +}); + +test('should add renameOperator if exists derived metrics', () => { [ ComparisonType.Difference, ComparisonType.Ratio, @@ -99,14 +115,14 @@ test('should skip renameOperator if exists derived metrics', () => { }, }, ), - ).toEqual(undefined); - }); -}); - -test('should add renameOperator', () => { - expect(renameOperator(formData, queryObject)).toEqual({ - operation: 'rename', - options: { columns: { 'count(*)': null }, inplace: true, level: 0 }, + ).toEqual({ + operation: 'rename', + options: { + columns: { [`${type}__count(*)__count(*)__1 year ago`]: '1 year ago' }, + inplace: true, + level: 0, + }, + }); }); }); @@ -170,6 +186,61 @@ test('should add renameOperator if exist "actual value" time comparison', () => }); }); +test('should add renameOperator if derived time comparison exists', () => { + expect( + renameOperator( + { + ...formData, + ...{ + comparison_type: ComparisonType.Ratio, + time_compare: ['1 year ago', '1 year later'], + }, + }, + queryObject, + ), + ).toEqual({ + operation: 'rename', + options: { + columns: { + 'ratio__count(*)__count(*)__1 year ago': '1 year ago', + 'ratio__count(*)__count(*)__1 year later': '1 year later', + }, + inplace: true, + level: 0, + }, + }); +}); + +test('should add renameOperator if multiple metrics exist', () => { + expect( + renameOperator( + { + ...formData, + ...{ + comparison_type: ComparisonType.Values, + time_compare: ['1 year ago'], + }, + }, + { + ...queryObject, + ...{ + metrics: ['count(*)', 'sum(sales)'], + }, + }, + ), + ).toEqual({ + operation: 'rename', + options: { + columns: { + 'count(*)__1 year ago': 'count(*), 1 year ago', + 'sum(sales)__1 year ago': 'sum(sales), 1 year ago', + }, + inplace: true, + level: 0, + }, + }); +}); + test('should remove renameOperator', () => { expect( renameOperator( diff --git a/superset-frontend/packages/superset-ui-core/package.json b/superset-frontend/packages/superset-ui-core/package.json index 9240c7287ae3..39f9bb48512c 100644 --- a/superset-frontend/packages/superset-ui-core/package.json +++ b/superset-frontend/packages/superset-ui-core/package.json @@ -41,7 +41,7 @@ "react-markdown": "^8.0.7", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", - "remark-gfm": "^4.0.0", + "remark-gfm": "^3.0.1", "reselect": "^4.0.0", "rison": "^0.1.1", "seedrandom": "^3.0.5", diff --git a/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts b/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts index 829440133a32..f953e4985886 100644 --- a/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts +++ b/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts @@ -67,6 +67,8 @@ type Hooks = { setDataMask?: SetDataMaskHook; /** handle tooltip */ setTooltip?: HandlerFunction; + /* handle legend scroll changes */ + onLegendScroll?: HandlerFunction; } & PlainObject; /** @@ -105,6 +107,8 @@ export interface ChartPropsConfig { inputRef?: RefObject; /** Theme object */ theme: SupersetTheme; + /* legend index */ + legendIndex?: number; } const DEFAULT_WIDTH = 800; @@ -135,6 +139,8 @@ export default class ChartProps { legendState?: LegendState; + legendIndex?: number; + queriesData: QueryData[]; width: number; @@ -164,6 +170,7 @@ export default class ChartProps { ownState = {}, filterState = {}, legendState, + legendIndex, initialValues = {}, queriesData = [], behaviors = [], @@ -190,6 +197,7 @@ export default class ChartProps { this.ownState = ownState; this.filterState = filterState; this.legendState = legendState; + this.legendIndex = legendIndex; this.behaviors = behaviors; this.displaySettings = displaySettings; this.appSection = appSection; @@ -215,6 +223,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector { input => input.ownState, input => input.filterState, input => input.legendState, + input => input.legendIndex, input => input.behaviors, input => input.displaySettings, input => input.appSection, @@ -235,6 +244,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector { ownState, filterState, legendState, + legendIndex, behaviors, displaySettings, appSection, @@ -255,6 +265,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector { ownState, filterState, legendState, + legendIndex, width, behaviors, displaySettings, diff --git a/superset-frontend/packages/superset-ui-core/src/connection/callApi/parseResponse.ts b/superset-frontend/packages/superset-ui-core/src/connection/callApi/parseResponse.ts index 52dc34808415..4ee81b80b3aa 100644 --- a/superset-frontend/packages/superset-ui-core/src/connection/callApi/parseResponse.ts +++ b/superset-frontend/packages/superset-ui-core/src/connection/callApi/parseResponse.ts @@ -57,11 +57,21 @@ export default async function parseResponse( const json = JSONbig.parse(rawData); const result: JsonResponse = { response, - // `json-bigint` could not handle floats well, see sidorares/json-bigint#62 - // TODO: clean up after json-bigint>1.0.1 is released - json: cloneDeepWith(json, (value: any) => - value?.isInteger?.() === false ? Number(value) : undefined, - ), + json: cloneDeepWith(json, (value: any) => { + if ( + value?.isInteger?.() === true && + (value?.isGreaterThan?.(Number.MAX_SAFE_INTEGER) || + value?.isLessThan?.(Number.MIN_SAFE_INTEGER)) + ) { + return BigInt(value); + } + // // `json-bigint` could not handle floats well, see sidorares/json-bigint#62 + // // TODO: clean up after json-bigint>1.0.1 is released + if (value?.isNaN?.() === false) { + return value?.toNumber?.(); + } + return undefined; + }), }; return result as ReturnType; } diff --git a/superset-frontend/packages/superset-ui-core/src/query/types/Metric.ts b/superset-frontend/packages/superset-ui-core/src/query/types/Metric.ts index 227ca6e71d56..229852373a87 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/types/Metric.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/types/Metric.ts @@ -17,7 +17,7 @@ * specific language governing permissions and limitations * under the License. */ -import { Maybe, QueryFormMetric } from '../../types'; +import { Currency, Maybe, QueryFormMetric } from '../../types'; import { Column } from './Column'; export type Aggregate = @@ -65,7 +65,7 @@ export interface Metric { certification_details?: Maybe; certified_by?: Maybe; d3format?: Maybe; - currency?: Maybe; + currency?: Maybe; description?: Maybe; is_certified?: boolean; verbose_name?: string; diff --git a/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts b/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts index 4a5f2a685908..49fc4b4363cd 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts @@ -31,7 +31,7 @@ import { Maybe } from '../../types'; import { PostProcessingRule } from './PostProcessing'; import { JsonObject } from '../../connection'; import { TimeGranularity } from '../../time-format'; -import { GenericDataType } from './QueryResponse'; +import { GenericDataType, DataRecordValue } from './QueryResponse'; export type BaseQueryObjectFilterClause = { col: QueryFormColumn; @@ -41,13 +41,13 @@ export type BaseQueryObjectFilterClause = { export type BinaryQueryObjectFilterClause = BaseQueryObjectFilterClause & { op: BinaryOperator; - val: string | number | boolean; + val: DataRecordValue; formattedVal?: string; }; export type SetQueryObjectFilterClause = BaseQueryObjectFilterClause & { op: SetOperator; - val: (string | number | boolean)[]; + val: DataRecordValue[]; formattedVal?: string[]; }; diff --git a/superset-frontend/packages/superset-ui-core/src/query/types/QueryResponse.ts b/superset-frontend/packages/superset-ui-core/src/query/types/QueryResponse.ts index b2a3c08cdfef..2e8943cff179 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/types/QueryResponse.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/types/QueryResponse.ts @@ -33,7 +33,7 @@ export enum GenericDataType { /** * Primitive types for data field values. */ -export type DataRecordValue = number | string | boolean | Date | null; +export type DataRecordValue = number | string | boolean | Date | null | bigint; export interface DataRecord { [key: string]: DataRecordValue; diff --git a/superset-frontend/packages/superset-ui-core/src/time-format/formatters/finestTemporalGrain.test.ts b/superset-frontend/packages/superset-ui-core/src/time-format/formatters/finestTemporalGrain.test.ts index 6e4f07df4b8b..0afd7e850f49 100644 --- a/superset-frontend/packages/superset-ui-core/src/time-format/formatters/finestTemporalGrain.test.ts +++ b/superset-frontend/packages/superset-ui-core/src/time-format/formatters/finestTemporalGrain.test.ts @@ -60,4 +60,12 @@ test('finestTemporalGrain', () => { expect(localTimeFormatter(new Date('2003-01-01 00:00:00Z').getTime())).toBe( '2002-12-31 19:00', ); + + const bigIntFormatter = finestTemporalGrain([ + BigInt(1234567890123456789n), + BigInt(1234567890123456789n), + ]); + expect(bigIntFormatter(new Date('2003-01-01 00:00:00Z').getTime())).toBe( + '2003', + ); }); diff --git a/superset-frontend/packages/superset-ui-core/src/time-format/formatters/finestTemporalGrain.ts b/superset-frontend/packages/superset-ui-core/src/time-format/formatters/finestTemporalGrain.ts index c03b7ec1593c..c3213f1cf93f 100644 --- a/superset-frontend/packages/superset-ui-core/src/time-format/formatters/finestTemporalGrain.ts +++ b/superset-frontend/packages/superset-ui-core/src/time-format/formatters/finestTemporalGrain.ts @@ -48,7 +48,11 @@ export default function finestTemporalGrain( } = useLocalTime ? localTimeUtils : utcUtils; let formatFunc = formatYear; + values.forEach((value: any) => { + if (typeof value === 'bigint') { + return; + } if (formatFunc === formatYear && isNotFirstMonth(value)) { formatFunc = formatMonth; } diff --git a/superset-frontend/packages/superset-ui-core/src/utils/lruCache.ts b/superset-frontend/packages/superset-ui-core/src/utils/lruCache.ts index f6785850c22a..e92005986aa3 100644 --- a/superset-frontend/packages/superset-ui-core/src/utils/lruCache.ts +++ b/superset-frontend/packages/superset-ui-core/src/utils/lruCache.ts @@ -67,6 +67,10 @@ class LRUCache { public get size() { return this.cache.size; } + + public values(): T[] { + return [...this.cache.values()]; + } } export function lruCache(capacity = 100) { diff --git a/superset-frontend/packages/superset-ui-core/src/utils/tooltip.ts b/superset-frontend/packages/superset-ui-core/src/utils/tooltip.ts index 6105af0f715a..59d5b08bb52a 100644 --- a/superset-frontend/packages/superset-ui-core/src/utils/tooltip.ts +++ b/superset-frontend/packages/superset-ui-core/src/utils/tooltip.ts @@ -17,6 +17,7 @@ * under the License. */ import { t } from '../translation'; +import { sanitizeHtml } from './html'; const TRUNCATION_STYLE = ` max-width: 300px; @@ -32,7 +33,7 @@ export function tooltipHtml( const titleRow = title ? `${title}` : ''; - return ` + return sanitizeHtml(`
${titleRow} @@ -53,5 +54,5 @@ export function tooltipHtml( }) .join('')}
-
`; + `); } diff --git a/superset-frontend/packages/superset-ui-core/test/connection/callApi/parseResponse.test.ts b/superset-frontend/packages/superset-ui-core/test/connection/callApi/parseResponse.test.ts index 789910c977be..4b6192e65ac0 100644 --- a/superset-frontend/packages/superset-ui-core/test/connection/callApi/parseResponse.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/connection/callApi/parseResponse.test.ts @@ -143,7 +143,7 @@ describe('parseResponse()', () => { const mockBigIntUrl = '/mock/get/bigInt'; const mockGetBigIntPayload = `{ "value": 9223372036854775807, "minus": { "value": -483729382918228373892, "str": "something" }, - "number": 1234, "floatValue": { "plus": 0.3452211361231223, "minus": -0.3452211361231223 }, + "number": 1234, "floatValue": { "plus": 0.3452211361231223, "minus": -0.3452211361231223, "even": 1234567890123456.0000000 }, "string.constructor": "data.constructor", "constructor": "constructor" }`; @@ -161,6 +161,7 @@ describe('parseResponse()', () => { expect(responseBigNumber.json.floatValue.minus).toEqual( -0.3452211361231223, ); + expect(responseBigNumber.json.floatValue.even).toEqual(1234567890123456); expect( responseBigNumber.json.floatValue.plus + responseBigNumber.json.floatValue.minus, diff --git a/superset-frontend/packages/superset-ui-core/test/utils/lruCache.test.ts b/superset-frontend/packages/superset-ui-core/test/utils/lruCache.test.ts index f8a077eba031..2c7f1fafa404 100644 --- a/superset-frontend/packages/superset-ui-core/test/utils/lruCache.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/utils/lruCache.test.ts @@ -35,8 +35,11 @@ test('LRU operations', () => { expect(cache.size).toBe(3); expect(cache.has('1')).toBeFalsy(); expect(cache.get('1')).toBeUndefined(); + expect(cache.values()).toEqual(['b', 'c', 'd']); cache.get('2'); + expect(cache.values()).toEqual(['c', 'd', 'b']); cache.set('5', 'e'); + expect(cache.values()).toEqual(['d', 'b', 'e']); expect(cache.has('2')).toBeTruthy(); expect(cache.has('3')).toBeFalsy(); // @ts-expect-error @@ -44,6 +47,7 @@ test('LRU operations', () => { // @ts-expect-error expect(() => cache.get(0)).toThrow(TypeError); expect(cache.size).toBe(3); + expect(cache.values()).toEqual(['d', 'b', 'e']); cache.clear(); expect(cache.size).toBe(0); expect(cache.capacity).toBe(3); diff --git a/superset-frontend/packages/superset-ui-core/test/utils/tooltip.test.ts b/superset-frontend/packages/superset-ui-core/test/utils/tooltip.test.ts index fbd29175ec41..1941519c0256 100644 --- a/superset-frontend/packages/superset-ui-core/test/utils/tooltip.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/utils/tooltip.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { tooltipHtml } from '@superset-ui/core'; +import { sanitizeHtml, tooltipHtml } from '@superset-ui/core'; const TITLE_STYLE = 'style="font-weight: 700;max-width:300px;overflow:hidden;text-overflow:ellipsis;"'; @@ -39,7 +39,8 @@ function removeWhitespaces(text: string) { test('should return a table with the given data', () => { const title = 'Title'; const html = removeWhitespaces(tooltipHtml(data, title)); - const expectedHtml = removeWhitespaces(` + const expectedHtml = removeWhitespaces( + sanitizeHtml(`
Title @@ -54,7 +55,8 @@ test('should return a table with the given data', () => {
3
-
`); + `), + ); expect(html).toMatch(expectedHtml); }); @@ -62,7 +64,8 @@ test('should return a table with the given data and a focused row', () => { const title = 'Title'; const focusedRow = 1; const html = removeWhitespaces(tooltipHtml(data, title, focusedRow)); - const expectedHtml = removeWhitespaces(` + const expectedHtml = removeWhitespaces( + sanitizeHtml(`
Title @@ -77,26 +80,30 @@ test('should return a table with the given data and a focused row', () => {
3
-
`); + `), + ); expect(html).toMatch(expectedHtml); }); test('should return a table with no data', () => { const title = 'Title'; const html = removeWhitespaces(tooltipHtml([], title)); - const expectedHtml = removeWhitespaces(` + const expectedHtml = removeWhitespaces( + sanitizeHtml(`
Title
No data
-
`); + `), + ); expect(html).toMatch(expectedHtml); }); test('should return a table with the given data and no title', () => { const html = removeWhitespaces(tooltipHtml(data)); - const expectedHtml = removeWhitespaces(` + const expectedHtml = removeWhitespaces( + sanitizeHtml(`
@@ -110,6 +117,36 @@ test('should return a table with the given data and no title', () => {
3
-
`); + `), + ); + expect(html).toMatch(expectedHtml); +}); + +test('should sanitize HTML input', () => { + const title = 'Title'; + const data = [ + ['B message', 'message2'], + ['', 'Italic'], + ]; + + const html = removeWhitespaces(tooltipHtml(data, title)); + + const expectedHtml = removeWhitespaces( + sanitizeHtml(` +
+ Titlealert("message"); + + + + + + + + + +
B messagemessage2
Italic
+
`), + ); + expect(html).toMatch(expectedHtml); }); diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/CategoricalDeckGLContainer.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/CategoricalDeckGLContainer.tsx index 8f3d1dac6122..4b49a7ce937c 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/CategoricalDeckGLContainer.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/CategoricalDeckGLContainer.tsx @@ -136,7 +136,7 @@ const CategoricalDeckGLContainer = (props: CategoricalDeckGLContainerProps) => { return data.map(d => { let color; if (fd.dimension) { - color = hexToRGB(colorFn(d.cat_color, fd.sliceId), c.a * 255); + color = hexToRGB(colorFn(d.cat_color, fd.slice_id), c.a * 255); return { ...d, color }; } diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/Multi/Multi.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/Multi/Multi.tsx index a0391de7d8b2..153a3e7917ee 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/Multi/Multi.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/Multi/Multi.tsx @@ -38,9 +38,20 @@ import { } from '../DeckGLContainer'; import { getExploreLongUrl } from '../utils/explore'; import layerGenerators from '../layers'; -import { Viewport } from '../utils/fitViewport'; +import fitViewport, { Viewport } from '../utils/fitViewport'; import { TooltipProps } from '../components/Tooltip'; +import { getPoints as getPointsArc } from '../layers/Arc/Arc'; +import { getPoints as getPointsPath } from '../layers/Path/Path'; +import { getPoints as getPointsPolygon } from '../layers/Polygon/Polygon'; +import { getPoints as getPointsGrid } from '../layers/Grid/Grid'; +import { getPoints as getPointsScatter } from '../layers/Scatter/Scatter'; +import { getPoints as getPointsContour } from '../layers/Contour/Contour'; +import { getPoints as getPointsHeatmap } from '../layers/Heatmap/Heatmap'; +import { getPoints as getPointsHex } from '../layers/Hex/Hex'; +import { getPoints as getPointsGeojson } from '../layers/Geojson/Geojson'; +import { getPoints as getPointsScreengrid } from '../layers/Screengrid/Screengrid'; + export type DeckMultiProps = { formData: QueryFormData; payload: JsonObject; @@ -56,7 +67,35 @@ export type DeckMultiProps = { const DeckMulti = (props: DeckMultiProps) => { const containerRef = useRef(); - const [viewport, setViewport] = useState(); + const getAdjustedViewport = useCallback(() => { + let viewport = { ...props.viewport }; + const points = [ + ...getPointsPolygon(props.payload.data.features.deck_polygon || []), + ...getPointsPath(props.payload.data.features.deck_path || []), + ...getPointsGrid(props.payload.data.features.deck_grid || []), + ...getPointsScatter(props.payload.data.features.deck_scatter || []), + ...getPointsContour(props.payload.data.features.deck_contour || []), + ...getPointsHeatmap(props.payload.data.features.deck_heatmap || []), + ...getPointsHex(props.payload.data.features.deck_hex || []), + ...getPointsArc(props.payload.data.features.deck_arc || []), + ...getPointsGeojson(props.payload.data.features.deck_geojson || []), + ...getPointsScreengrid(props.payload.data.features.deck_screengrid || []), + ]; + + if (props.formData) { + viewport = fitViewport(viewport, { + width: props.width, + height: props.height, + points, + }); + } + if (viewport.zoom < 0) { + viewport.zoom = 0; + } + return viewport; + }, [props]); + + const [viewport, setViewport] = useState(getAdjustedViewport()); const [subSlicesLayers, setSubSlicesLayers] = useState>( {}, ); @@ -70,23 +109,31 @@ const DeckMulti = (props: DeckMultiProps) => { const loadLayers = useCallback( (formData: QueryFormData, payload: JsonObject, viewport?: Viewport) => { - setViewport(viewport); + setViewport(getAdjustedViewport()); setSubSlicesLayers({}); payload.data.slices.forEach( (subslice: { slice_id: number } & JsonObject) => { // Filters applied to multi_deck are passed down to underlying charts // note that dashboard contextual information (filter_immune_slices and such) aren't // taken into consideration here - const filters = [ - ...(subslice.form_data.filters || []), - ...(formData.filters || []), + const extra_filters = [ + ...(subslice.form_data.extra_filters || []), ...(formData.extra_filters || []), + ...(formData.extra_form_data?.filters || []), + ]; + + const adhoc_filters = [ + ...(formData.adhoc_filters || []), + ...(subslice.formData?.adhoc_filters || []), + ...(formData.extra_form_data?.adhoc_filters || []), ]; + const subsliceCopy = { ...subslice, form_data: { ...subslice.form_data, - filters, + extra_filters, + adhoc_filters, }, }; @@ -117,7 +164,13 @@ const DeckMulti = (props: DeckMultiProps) => { }, ); }, - [props.datasource, props.onAddFilter, props.onSelect, setTooltip], + [ + props.datasource, + props.onAddFilter, + props.onSelect, + setTooltip, + getAdjustedViewport, + ], ); const prevDeckSlices = usePrevious(props.formData.deck_slices); @@ -136,7 +189,7 @@ const DeckMulti = (props: DeckMultiProps) => { { points.push(d.sourcePosition); @@ -77,7 +77,7 @@ export function getLayer( getTargetColor: (d: any) => d.targetColor || d.color || [tc.r, tc.g, tc.b, 255 * tc.a], id: `path-layer-${fd.slice_id}` as const, - strokeWidth: fd.stroke_width ? fd.stroke_width : 3, + getWidth: fd.stroke_width ? fd.stroke_width : 3, ...commonLayerProps(fd, setTooltip, setTooltipContent(fd)), }); } diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/Contour.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/Contour.tsx index 65ca8b3eca71..b8dacb70de09 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/Contour.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/Contour.tsx @@ -97,7 +97,7 @@ export const getLayer: getLayerType = function ( }); }; -function getPoints(data: any[]) { +export function getPoints(data: any[]) { return data.map(d => d.position); } diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Geojson/Geojson.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Geojson/Geojson.tsx index 6960fed80121..cdcdbf3b0f1e 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Geojson/Geojson.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Geojson/Geojson.tsx @@ -39,6 +39,7 @@ import { commonLayerProps } from '../common'; import TooltipRow from '../../TooltipRow'; import fitViewport, { Viewport } from '../../utils/fitViewport'; import { TooltipProps } from '../../components/Tooltip'; +import { Point } from '../../types'; type ProcessedFeature = Feature & { properties: JsonObject; @@ -172,6 +173,17 @@ export type DeckGLGeoJsonProps = { width: number; }; +export function getPoints(data: Point[]) { + return data.reduce((acc: Array, feature: any) => { + const bounds = geojsonExtent(feature); + if (bounds) { + return [...acc, [bounds[0], bounds[1]], [bounds[2], bounds[3]]]; + } + + return acc; + }, []); +} + const DeckGLGeoJson = (props: DeckGLGeoJsonProps) => { const containerRef = useRef(); const setTooltip = useCallback((tooltip: TooltipProps['tooltip']) => { @@ -186,24 +198,13 @@ const DeckGLGeoJson = (props: DeckGLGeoJsonProps) => { const viewport: Viewport = useMemo(() => { if (formData.autozoom) { - const points = - payload?.data?.features?.reduce?.( - (acc: [number, number, number, number][], feature: any) => { - const bounds = geojsonExtent(feature); - if (bounds) { - return [...acc, [bounds[0], bounds[1]], [bounds[2], bounds[3]]]; - } - - return acc; - }, - [], - ) || []; + const points = getPoints(payload.data.features) || []; if (points.length) { return fitViewport(props.viewport, { width, height, - points, + points: getPoints(payload.data.features) || [], }); } } diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/Grid.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/Grid.tsx index 3bce514453c6..d2c86bd87828 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/Grid.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/Grid.tsx @@ -86,7 +86,7 @@ export function getLayer( }); } -function getPoints(data: JsonObject[]) { +export function getPoints(data: JsonObject[]) { return data.map(d => d.position); } diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/Heatmap.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/Heatmap.tsx index d84b3d1b2341..5fe927762772 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/Heatmap.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/Heatmap.tsx @@ -79,7 +79,7 @@ export const getLayer: getLayerType = ( }); }; -function getPoints(data: any[]) { +export function getPoints(data: any[]) { return data.map(d => d.position); } diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/Hex.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/Hex.tsx index 3a27f4436ae1..ef79b1fa07e0 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/Hex.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/Hex.tsx @@ -84,7 +84,7 @@ export function getLayer( }); } -function getPoints(data: JsonObject[]) { +export function getPoints(data: JsonObject[]) { return data.map(d => d.position); } diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Path/Path.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Path/Path.tsx index 60663343afde..379247cee1a1 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Path/Path.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Path/Path.tsx @@ -76,7 +76,7 @@ export function getLayer( }); } -function getPoints(data: JsonObject[]) { +export function getPoints(data: JsonObject[]) { let points: Point[] = []; data.forEach(d => { points = points.concat(d.path); diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Polygon/Polygon.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Polygon/Polygon.tsx index 7aaf53fe9a2d..8b355aa97292 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Polygon/Polygon.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Polygon/Polygon.tsx @@ -173,6 +173,10 @@ export type DeckGLPolygonProps = { height: number; }; +export function getPoints(data: JsonObject[]) { + return data.flatMap(getPointsFromPolygon); +} + const DeckGLPolygon = (props: DeckGLPolygonProps) => { const containerRef = useRef(); @@ -183,7 +187,7 @@ const DeckGLPolygon = (props: DeckGLPolygonProps) => { viewport = fitViewport(viewport, { width: props.width, height: props.height, - points: features.flatMap(getPointsFromPolygon), + points: getPoints(features), }); } if (viewport.zoom < 0) { diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/Scatter.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/Scatter.tsx index 5f3ac36082d9..755b4ae7791c 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/Scatter.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/Scatter.tsx @@ -30,7 +30,7 @@ import TooltipRow from '../../TooltipRow'; import { unitToRadius } from '../../utils/geo'; import { TooltipProps } from '../../components/Tooltip'; -function getPoints(data: JsonObject[]) { +export function getPoints(data: JsonObject[]) { return data.map(d => d.position); } diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/Screengrid.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/Screengrid.tsx index d0153688b564..f6a011bc93f0 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/Screengrid.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/Screengrid.tsx @@ -35,7 +35,7 @@ import { } from '../../DeckGLContainer'; import { TooltipProps } from '../../components/Tooltip'; -function getPoints(data: JsonObject[]) { +export function getPoints(data: JsonObject[]) { return data.map(d => d.position); } diff --git a/superset-frontend/plugins/plugin-chart-echarts/package.json b/superset-frontend/plugins/plugin-chart-echarts/package.json index f84ecd0ed4ac..01763f3078af 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/package.json +++ b/superset-frontend/plugins/plugin-chart-echarts/package.json @@ -24,9 +24,10 @@ "lib" ], "dependencies": { + "@types/react-redux": "^7.1.10", "d3-array": "^1.2.0", - "lodash": "^4.17.21", - "dayjs": "^1.11.13" + "dayjs": "^1.11.13", + "lodash": "^4.17.21" }, "peerDependencies": { "@superset-ui/chart-controls": "*", diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts index b434fbbc58e4..2dfa82aefa2c 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts @@ -18,6 +18,7 @@ */ import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; +import { Metric } from '@superset-ui/chart-controls'; import { ChartProps, getMetricLabel, @@ -100,6 +101,13 @@ export default function transformProps(chartProps: ChartProps) { adhoc_filter.operator === 'TEMPORAL_RANGE', )?.[0]; + let metricEntry: Metric | undefined; + if (chartProps.datasource?.metrics) { + metricEntry = chartProps.datasource.metrics.find( + metricItem => metricItem.metric_name === metric, + ); + } + const isCustomOrInherit = timeComparison === 'custom' || timeComparison === 'inherit'; let dataOffset: string[] = []; @@ -140,7 +148,7 @@ export default function transformProps(chartProps: ChartProps) { metric, currencyFormats, columnFormats, - yAxisFormat, + metricEntry?.d3format || yAxisFormat, currencyFormat, ); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts index 757ecd3e612f..9275697f99c7 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts @@ -80,7 +80,7 @@ export default function transformProps( metric, currencyFormats, columnFormats, - yAxisFormat, + metricEntry?.d3format || yAxisFormat, currencyFormat, ); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx index d7882ccdb6c1..d95ae633af09 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx @@ -98,6 +98,7 @@ class BigNumberVis extends PureComponent { !formatTime || !showTimestamp || typeof timestamp === 'string' || + typeof timestamp === 'bigint' || typeof timestamp === 'boolean' ) return null; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts index d285a551b136..846930fe7a19 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts @@ -160,7 +160,7 @@ export default function transformProps( metric, currencyFormats, columnFormats, - yAxisFormat, + metricEntry?.d3format || yAxisFormat, currencyFormat, ); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts index 1888383a5232..49e51f511b38 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts @@ -201,7 +201,7 @@ export default function transformProps(chartProps: EchartsBubbleChartProps) { name: bubbleXAxisTitle, nameLocation: 'middle', nameTextStyle: { - fontWight: 'bolder', + fontWeight: 'bolder', }, nameGap: convertInteger(xAxisTitleMargin), type: xAxisType, @@ -219,7 +219,7 @@ export default function transformProps(chartProps: EchartsBubbleChartProps) { name: bubbleYAxisTitle, nameLocation: 'middle', nameTextStyle: { - fontWight: 'bolder', + fontWeight: 'bolder', }, nameGap: convertInteger(yAxisTitleMargin), min: yAxisMin, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/transformProps.ts index 5a3cd7587009..575e4c70024a 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Heatmap/transformProps.ts @@ -145,10 +145,10 @@ export default function transformProps( data: data.map(row => colnames.map(col => { const value = row[col]; - if (!value) { + if (value === null || value === undefined) { return NULL_STRING; } - if (typeof value === 'boolean') { + if (typeof value === 'boolean' || typeof value === 'bigint') { return String(value); } return value; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/controlPanel.tsx index a347694f1bfb..d4e8d4b86dbc 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/controlPanel.tsx @@ -27,7 +27,9 @@ import { formatSelectOptionsForRange, dndGroupByControl, columnsByType, - sections, + D3_FORMAT_OPTIONS, + D3_FORMAT_DOCS, + D3_NUMBER_FORMAT_DESCRIPTION_VALUES_TEXT, } from '@superset-ui/chart-controls'; import { showLegendControl, showValueControl } from '../controls'; @@ -105,7 +107,6 @@ const config: ControlPanelConfig = { ], ], }, - sections.titleControls, { label: t('Chart Options'), expanded: true, @@ -113,6 +114,58 @@ const config: ControlPanelConfig = { ['color_scheme'], [showValueControl], [showLegendControl], + [ + { + name: 'x_axis_title', + config: { + type: 'TextControl', + label: t('X Axis Title'), + renderTrigger: true, + default: '', + description: t('Changing this control takes effect instantly'), + }, + }, + ], + [ + { + name: 'x_axis_format', + config: { + type: 'SelectControl', + freeForm: true, + label: t('X Axis Format'), + renderTrigger: true, + default: 'SMART_NUMBER', + choices: D3_FORMAT_OPTIONS, + description: `${D3_FORMAT_DOCS} ${D3_NUMBER_FORMAT_DESCRIPTION_VALUES_TEXT}`, + }, + }, + ], + [ + { + name: 'y_axis_title', + config: { + type: 'TextControl', + label: t('Y Axis Title'), + renderTrigger: true, + default: '', + description: t('Changing this control takes effect instantly'), + }, + }, + ], + [ + { + name: 'y_axis_format', + config: { + type: 'SelectControl', + freeForm: true, + label: t('Y Axis Format'), + renderTrigger: true, + default: 'SMART_NUMBER', + choices: D3_FORMAT_OPTIONS, + description: `${D3_FORMAT_DOCS} ${D3_NUMBER_FORMAT_DESCRIPTION_VALUES_TEXT}`, + }, + }, + ], ], }, ], diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/transformProps.ts index df8fe3b1569f..28d2a93e6d11 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/transformProps.ts @@ -25,7 +25,7 @@ import { CategoricalColorNamespace, NumberFormats, getColumnLabel, - getNumberFormatter, + getValueFormatter, tooltipHtml, } from '@superset-ui/core'; import { HistogramChartProps, HistogramTransformedProps } from './types'; @@ -41,6 +41,7 @@ export default function transformProps( const refs: Refs = {}; let focusedSeries: number | undefined; const { + datasource: { currencyFormats = {}, columnFormats = {} }, formData, height, hooks, @@ -58,19 +59,33 @@ export default function transformProps( showLegend, showValue, sliceId, + xAxisFormat, xAxisTitle, yAxisTitle, + yAxisFormat, } = formData; const { data } = queriesData[0]; const colorFn = CategoricalColorNamespace.getScale(colorScheme); - const formatter = getNumberFormatter( - normalize ? NumberFormats.FLOAT_2_POINT : NumberFormats.INTEGER, - ); + + const formatter = (format: string) => + getValueFormatter( + column, + currencyFormats, + columnFormats, + format, + undefined, + ); + const xAxisFormatter = formatter(xAxisFormat); + const yAxisFormatter = formatter(yAxisFormat); + const percentFormatter = getPercentFormatter(NumberFormats.PERCENT_2_POINT); const groupbySet = new Set(groupby); - const xAxisData: string[] = Object.keys(data[0]).filter( - key => !groupbySet.has(key), - ); + const xAxisData: string[] = Object.keys(data[0]) + .filter(key => !groupbySet.has(key)) + .map(key => { + const array = key.split(' - ').map(value => parseFloat(value)); + return `${xAxisFormatter(array[0])} - ${xAxisFormatter(array[1])}`; + }); const barSeries: BarSeriesOption[] = data.map(datum => { const seriesName = groupby.length > 0 @@ -91,7 +106,7 @@ export default function transformProps( position: 'top', formatter: params => { const { value } = params; - return formatter.format(value as number); + return yAxisFormatter.format(value as number); }, }, }; @@ -108,7 +123,7 @@ export default function transformProps( const title = params[0].name; const rows = params.map(param => { const { marker, seriesName, value } = param; - return [`${marker}${seriesName}`, formatter.format(value as number)]; + return [`${marker}${seriesName}`, yAxisFormatter.format(value as number)]; }); if (groupby.length > 0) { const total = params.reduce( @@ -122,7 +137,7 @@ export default function transformProps( ), ); } - const totalRow = ['Total', formatter.format(total)]; + const totalRow = ['Total', yAxisFormatter.format(total)]; if (!normalize) { totalRow.push(percentFormatter.format(1)); } @@ -159,7 +174,7 @@ export default function transformProps( type: 'value', nameLocation: 'middle', axisLabel: { - formatter: (value: number) => formatter.format(value), + formatter: (value: number) => yAxisFormatter.format(value), }, }, series: barSeries, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/types.ts index ca6c16d79ee7..ab3d4b75c2eb 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/types.ts @@ -28,7 +28,9 @@ export type HistogramFormData = QueryFormData & { sliceId: number; showLegend: boolean; showValue: boolean; + xAxisFormat: string; xAxisTitle: string; + yAxisFormat: string; yAxisTitle: string; }; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Sankey/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Sankey/transformProps.ts index c3db5052bf12..9ef7389660a9 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Sankey/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Sankey/transformProps.ts @@ -73,13 +73,25 @@ export default function transformProps( })); // stores a map with the total values for each node considering the links - const nodeValues = new Map(); + const incomingFlows = new Map(); + const outgoingFlows = new Map(); + const allNodeNames = new Set(); + links.forEach(link => { const { source, target, value } = link; - const sourceValue = nodeValues.get(source) || 0; - const targetValue = nodeValues.get(target) || 0; - nodeValues.set(source, sourceValue + value); - nodeValues.set(target, targetValue + value); + allNodeNames.add(source); + allNodeNames.add(target); + incomingFlows.set(target, (incomingFlows.get(target) || 0) + value); + outgoingFlows.set(source, (outgoingFlows.get(source) || 0) + value); + }); + + const nodeValues = new Map(); + + allNodeNames.forEach(nodeName => { + const totalIncoming = incomingFlows.get(nodeName) || 0; + const totalOutgoing = outgoingFlows.get(nodeName) || 0; + + nodeValues.set(nodeName, Math.max(totalIncoming, totalOutgoing)); }); const tooltipFormatter = (params: CallbackDataParams) => { diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx index b91db0b4c1b8..7bfab8e3ef84 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx @@ -57,6 +57,7 @@ export default function EchartsTimeseries({ refs, emitCrossFilters, coltypeMapping, + onLegendScroll, }: TimeseriesChartTransformedProps) { const { stack } = formData; const echartRef = useRef(null); @@ -159,6 +160,9 @@ export default function EchartsTimeseries({ mouseover: params => { onFocusedSeries(params.seriesName); }, + legendscroll: payload => { + onLegendScroll?.(payload.scrollDataIndex); + }, legendselectchanged: payload => { onLegendStateChanged?.(payload.selected); }, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx index ee2d9a6c5cf5..55cd48736a0b 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx @@ -122,7 +122,7 @@ function createAxisTitleControl(axis: 'x' | 'y'): ControlSetRow[] { clearable: true, label: t('AXIS TITLE MARGIN'), renderTrigger: true, - default: sections.TITLE_MARGIN_OPTIONS[0], + default: sections.TITLE_MARGIN_OPTIONS[1], choices: formatSelectOptions(sections.TITLE_MARGIN_OPTIONS), description: t('Changing this control takes effect instantly'), visibility: ({ controls }: ControlPanelsContainerProps) => diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts index 219df879e7fa..bb62d6f128c5 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts @@ -122,6 +122,7 @@ export default function transformProps( theme, inContextMenu, emitCrossFilters, + legendIndex, } = chartProps; let focusedSeries: string | null = null; @@ -450,6 +451,7 @@ export default function transformProps( setControlValue = () => {}, onContextMenu, onLegendStateChanged, + onLegendScroll, } = hooks; const addYAxisLabelOffset = !!yAxisTitle; @@ -622,7 +624,9 @@ export default function transformProps( theme, zoomable, legendState, + padding, ), + scrollDataIndex: legendIndex || 0, data: legendData as string[], }, series: dedupSeries(reorderForecastSeries(series) as SeriesOption[]), @@ -691,5 +695,6 @@ export default function transformProps( }, refs, coltypeMapping: dataTypes, + onLegendScroll, }; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts index cadf647484df..0abefc95e3d2 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts @@ -226,7 +226,7 @@ export function transformSeries( stackId = forecastSeries.name; } else if (stack && isObservation) { // the suffix of the observation series is '' (falsy), which disables - // stacking. Therefore we need to set something that is truthy. + // stacking. Therefore, we need to set something that is truthy. stackId = getTimeCompareStackId('obs', timeCompare, name); } else if (stack && isTrend) { stackId = getTimeCompareStackId(forecastSeries.type, timeCompare, name); @@ -322,6 +322,15 @@ export function transformSeries( show: !!showValue, position: isHorizontal ? 'right' : 'top', formatter: (params: any) => { + // don't show confidence band value labels, as they're already visible on the tooltip + if ( + [ + ForecastSeriesEnum.ForecastUpper, + ForecastSeriesEnum.ForecastLower, + ].includes(forecastSeries.type) + ) { + return ''; + } const { value, dataIndex, seriesIndex, seriesName } = params; const numericValue = isHorizontal ? value[0] : value[1]; const isSelectedLegend = !legendState || legendState[seriesName]; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Tree/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Tree/controlPanel.tsx index 34d65a27d315..4d8b0fa978ee 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Tree/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Tree/controlPanel.tsx @@ -89,9 +89,9 @@ const controlPanel: ControlPanelConfig = { { name: 'metric', config: { - ...optionalEntity, - type: 'DndMetricSelect', - label: t('Metric'), + ...sharedControls.metric, + clearable: true, + validators: [], description: t('Metric for node values'), }, }, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx index 995e3a535134..e93327a696f6 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx @@ -25,10 +25,13 @@ import { useLayoutEffect, useCallback, Ref, + useState, } from 'react'; +import { useSelector } from 'react-redux'; + import { styled } from '@superset-ui/core'; -import { use, init, EChartsType } from 'echarts/core'; +import { use, init, EChartsType, registerLocale } from 'echarts/core'; import { SankeyChart, PieChart, @@ -60,6 +63,15 @@ import { } from 'echarts/components'; import { LabelLayout } from 'echarts/features'; import { EchartsHandler, EchartsProps, EchartsStylesProps } from '../types'; +import { DEFAULT_LOCALE } from '../constants'; + +// Define this interface here to avoid creating a dependency back to superset-frontend, +// TODO: to move the type to @superset-ui/core +interface ExplorePageState { + common: { + locale: string; + }; +} const Styles = styled.div` height: ${({ height }) => height}; @@ -95,6 +107,16 @@ use([ LabelLayout, ]); +const loadLocale = async (locale: string) => { + let lang; + try { + lang = await import(`echarts/lib/i18n/lang${locale}`); + } catch (e) { + console.error(`Locale ${locale} not supported in ECharts`, e); + } + return lang?.default; +}; + function Echart( { width, @@ -112,6 +134,7 @@ function Echart( // eslint-disable-next-line no-param-reassign refs.divRef = divRef; } + const [didMount, setDidMount] = useState(false); const chartRef = useRef(); const currentSelection = useMemo( () => Object.keys(selectedValues) || [], @@ -123,24 +146,52 @@ function Echart( getEchartInstance: () => chartRef.current, })); - useEffect(() => { - if (!divRef.current) return; - if (!chartRef.current) { - chartRef.current = init(divRef.current); - } + const locale = useSelector( + (state: ExplorePageState) => state?.common?.locale ?? DEFAULT_LOCALE, + ).toUpperCase(); - Object.entries(eventHandlers || {}).forEach(([name, handler]) => { - chartRef.current?.off(name); - chartRef.current?.on(name, handler); - }); + const handleSizeChange = useCallback( + ({ width, height }: { width: number; height: number }) => { + if (chartRef.current) { + chartRef.current.resize({ width, height }); + } + }, + [], + ); - Object.entries(zrEventHandlers || {}).forEach(([name, handler]) => { - chartRef.current?.getZr().off(name); - chartRef.current?.getZr().on(name, handler); + useEffect(() => { + loadLocale(locale).then(localeObj => { + if (localeObj) { + registerLocale(locale, localeObj); + } + if (!divRef.current) return; + if (!chartRef.current) { + chartRef.current = init(divRef.current, null, { locale }); + } + setDidMount(true); }); + }, [locale]); - chartRef.current.setOption(echartOptions, true); - }, [echartOptions, eventHandlers, zrEventHandlers]); + useEffect(() => { + if (didMount) { + Object.entries(eventHandlers || {}).forEach(([name, handler]) => { + chartRef.current?.off(name); + chartRef.current?.on(name, handler); + }); + + Object.entries(zrEventHandlers || {}).forEach(([name, handler]) => { + chartRef.current?.getZr().off(name); + chartRef.current?.getZr().on(name, handler); + }); + + chartRef.current?.setOption(echartOptions, true); + + // did mount + handleSizeChange({ width, height }); + } + }, [didMount, echartOptions, eventHandlers, zrEventHandlers]); + + useEffect(() => () => chartRef.current?.dispose(), []); // highlighting useEffect(() => { @@ -158,22 +209,7 @@ function Echart( }); } previousSelection.current = currentSelection; - }, [currentSelection]); - - const handleSizeChange = useCallback( - ({ width, height }: { width: number; height: number }) => { - if (chartRef.current) { - chartRef.current.resize({ width, height }); - } - }, - [], - ); - - // did mount - useEffect(() => { - handleSizeChange({ width, height }); - return () => chartRef.current?.dispose(); - }, []); + }, [currentSelection, chartRef.current]); useLayoutEffect(() => { handleSizeChange({ width, height }); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts index 65ea1679e2c1..fb6221342a40 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts @@ -121,3 +121,5 @@ export const TOOLTIP_POINTER_MARGIN = 10; // If no satisfactory position can be found, how far away // from the edge of the window should the tooltip be kept export const TOOLTIP_OVERFLOW_MARGIN = 5; + +export const DEFAULT_LOCALE = 'en'; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx index db7daa02b675..5c52bb1762c3 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx @@ -230,7 +230,7 @@ const tooltipPercentageControl: ControlSetItem = { type: 'CheckboxControl', label: t('Show percentage'), renderTrigger: true, - default: true, + default: false, description: t('Whether to display the percentage value in the tooltip'), visibility: ({ controls, form_data }: ControlPanelsContainerProps) => Boolean(controls?.rich_tooltip?.value) && diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts index 02adce8cc577..1a45714b3c8d 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts @@ -138,6 +138,7 @@ export interface BaseTransformedProps { width: number; emitCrossFilters?: boolean; coltypeMapping?: Record; + onLegendScroll?: (currentIndex: number) => void; } export type CrossFilterTransformedProps = { @@ -183,7 +184,7 @@ export class EchartsChartPlugin< super({ ...restProps, metadata: new ChartMetadata({ - parseMethod: 'json', + parseMethod: 'json-bigint', ...metadata, }), }); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts index 8d93783c8aa1..65961e0889c7 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts @@ -78,7 +78,7 @@ export function getTooltipTimeFormatter( format?: string, ): TimeFormatter | StringConstructor { if (format === SMART_DATE_ID) { - return getSmartDateDetailedFormatter(); + return getSmartDateVerboseFormatter(); } if (format) { return getTimeFormatter(format); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts index 0aa0ae988ee9..157ab46f4835 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts @@ -33,7 +33,7 @@ import { TimeFormatter, ValueFormatter, } from '@superset-ui/core'; -import { SortSeriesType } from '@superset-ui/chart-controls'; +import { SortSeriesType, LegendPaddingType } from '@superset-ui/chart-controls'; import { format } from 'echarts/core'; import type { LegendComponentOption } from 'echarts/components'; import type { SeriesOption } from 'echarts'; @@ -156,9 +156,15 @@ export function sortAndFilterSeries( case SortSeriesType.Avg: aggregator = name => ({ name, value: meanBy(rows, name) }); break; - default: - aggregator = name => ({ name, value: name.toLowerCase() }); - break; + default: { + const collator = new Intl.Collator(undefined, { + numeric: true, + sensitivity: 'base', + }); + return seriesNames.sort((a, b) => + sortSeriesAscending ? collator.compare(a, b) : collator.compare(b, a), + ); + } } const sortedValues = seriesNames.map(aggregator); @@ -363,7 +369,7 @@ export function formatSeriesName( if (name === undefined || name === null) { return NULL_STRING; } - if (typeof name === 'boolean') { + if (typeof name === 'boolean' || typeof name === 'bigint') { return name.toString(); } if (name instanceof Date || coltype === GenericDataType.Temporal) { @@ -425,6 +431,7 @@ export function getLegendProps( theme: SupersetTheme, zoomable = false, legendState?: LegendState, + padding?: LegendPaddingType, ): LegendComponentOption | LegendComponentOption[] { const legend: LegendComponentOption | LegendComponentOption[] = { orient: [LegendOrientation.Top, LegendOrientation.Bottom].includes( @@ -443,13 +450,30 @@ export function getLegendProps( borderColor: theme.colors.grayscale.base, }, }; + const MIN_LEGEND_WIDTH = 0; + const MARGIN_GUTTER = 45; + const getLegendWidth = (paddingWidth: number) => + Math.max(paddingWidth - MARGIN_GUTTER, MIN_LEGEND_WIDTH); + switch (orientation) { case LegendOrientation.Left: legend.left = 0; + if (padding?.left) { + legend.textStyle = { + overflow: 'truncate', + width: getLegendWidth(padding.left), + }; + } break; case LegendOrientation.Right: legend.right = 0; legend.top = zoomable ? TIMESERIES_CONSTANTS.legendRightTopOffset : 0; + if (padding?.right) { + legend.textStyle = { + overflow: 'truncate', + width: getLegendWidth(padding.right), + }; + } break; case LegendOrientation.Bottom: legend.bottom = 0; @@ -467,7 +491,7 @@ export function getChartPadding( show: boolean, orientation: LegendOrientation, margin?: string | number | null, - padding?: { top?: number; bottom?: number; left?: number; right?: number }, + padding?: LegendPaddingType, isHorizontal?: boolean, ): { bottom: number; diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/BigNumber/transformProps.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/BigNumber/transformProps.test.ts index 8c9ee5621c52..8b0bf3552585 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/BigNumber/transformProps.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/BigNumber/transformProps.test.ts @@ -173,7 +173,7 @@ describe('BigNumberWithTrendline', () => { label: 'value', metric_name: 'value', d3format: '.2f', - currency: `{symbol: 'USD', symbolPosition: 'prefix' }`, + currency: { symbol: 'USD', symbolPosition: 'prefix' }, }, ], }, diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/index.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/index.test.ts index 7061dc890768..da0e8d2cb106 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/index.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/index.test.ts @@ -125,6 +125,6 @@ test('@superset-ui/plugin-chart-echarts-parsemethod-validation', () => { ]; plugins.forEach(plugin => { - expect(plugin.metadata.parseMethod).toEqual('json'); + expect(plugin.metadata.parseMethod).toEqual('json-bigint'); }); }); diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts index 7054f6019ad3..67a0bab9e605 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts @@ -67,6 +67,39 @@ const sortData: DataRecord[] = [ { my_x_axis: null, x: 4, y: 3, z: 7 }, ]; +const sortDataWithNumbers: DataRecord[] = [ + { + my_x_axis: 'my_axis', + '9. September': 6, + 6: 1, + '11. November': 8, + 8: 2, + '10. October': 1, + 10: 4, + '3. March': 2, + '8. August': 6, + 2: 1, + 12: 3, + 9: 1, + '1. January': 1, + '4. April': 12, + '2. February': 9, + 5: 4, + 3: 1, + 11: 2, + '12. December': 4, + 1: 7, + '6. June': 1, + 4: 5, + 7: 2, + c: 0, + '7. July': 2, + d: 0, + '5. May': 4, + a: 1, + }, +]; + const totalStackedValues = [3, 15, 14]; test('sortRows by name ascending', () => { @@ -288,6 +321,84 @@ test('sortAndFilterSeries by name descending', () => { sortAndFilterSeries(sortData, 'my_x_axis', [], SortSeriesType.Name, false), ).toEqual(['z', 'y', 'x']); }); +test('sortAndFilterSeries by name with numbers asc', () => { + expect( + sortAndFilterSeries( + sortDataWithNumbers, + 'my_x_axis', + [], + SortSeriesType.Name, + true, + ), + ).toEqual([ + '1', + '1. January', + '2', + '2. February', + '3', + '3. March', + '4', + '4. April', + '5', + '5. May', + '6', + '6. June', + '7', + '7. July', + '8', + '8. August', + '9', + '9. September', + '10', + '10. October', + '11', + '11. November', + '12', + '12. December', + 'a', + 'c', + 'd', + ]); +}); +test('sortAndFilterSeries by name with numbers desc', () => { + expect( + sortAndFilterSeries( + sortDataWithNumbers, + 'my_x_axis', + [], + SortSeriesType.Name, + false, + ), + ).toEqual([ + 'd', + 'c', + 'a', + '12. December', + '12', + '11. November', + '11', + '10. October', + '10', + '9. September', + '9', + '8. August', + '8', + '7. July', + '7', + '6. June', + '6', + '5. May', + '5', + '4. April', + '4', + '3. March', + '3', + '2. February', + '2', + '1. January', + '1', + ]); +}); describe('extractSeries', () => { it('should generate a valid ECharts timeseries series object', () => { diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/handlebarTemplate.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/handlebarTemplate.tsx index 71b5b9b067a6..22281f09bfbc 100644 --- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/handlebarTemplate.tsx +++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/handlebarTemplate.tsx @@ -65,6 +65,7 @@ export const handlebarsTemplateControlSetItem: ControlSetItem = { `, isInt: false, renderTrigger: true, + valueKey: null, validators: [validateNonEmpty], mapStateToProps: ({ controls }) => ({ diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/style.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/style.tsx index b5f8dc93827a..9723228146dd 100644 --- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/style.tsx +++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/style.tsx @@ -75,6 +75,7 @@ export const styleControlSetItem: ControlSetItem = { description: t('CSS applied to the chart'), isInt: false, renderTrigger: true, + valueKey: null, validators: [], mapStateToProps: ({ controls }) => ({ diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx b/superset-frontend/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx index aef8b8b32126..a17bac64aa78 100644 --- a/superset-frontend/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx +++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx @@ -51,7 +51,6 @@ const Styles = styled.div` width: ${ typeof width === 'string' ? parseInt(width, 10) : width - margin * 2 }px; - white-space: nowrap; `} `; diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx index 47d442bed559..f18c6c41f550 100644 --- a/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx @@ -442,7 +442,7 @@ const config: ControlPanelConfig = { renderTrigger: true, default: true, description: t( - 'Renders table cells as HTML when applicable. For example, HTML <a> tags will be rendered as hyperlinks.', + 'Renders table cells as HTML when applicable. For example, HTML
tags will be rendered as hyperlinks.', ), }, }, diff --git a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx index 77905ea562ed..95121a317d48 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx +++ b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx @@ -605,6 +605,8 @@ export default function TableChart( // Calculate the number of placeholder columns needed before the current header const startPosition = value[0]; const colSpan = value.length; + // Retrieve the originalLabel from the first column in this group + const originalLabel = columnsMeta[value[0]]?.originalLabel || key; // Add placeholder for columns before this header for (let i = currentColumnIndex; i < startPosition; i += 1) { @@ -620,7 +622,7 @@ export default function TableChart( // Add the current header headers.push( - {key} + {originalLabel} ( ), Footer: totals ? ( i === 0 ? ( - +
(
) : ( - + {formatColumnValue(column, totals[key])[1]} ) diff --git a/superset-frontend/plugins/plugin-chart-table/src/buildQuery.ts b/superset-frontend/plugins/plugin-chart-table/src/buildQuery.ts index 7068ab119304..5b9cb684ed4a 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/buildQuery.ts +++ b/superset-frontend/plugins/plugin-chart-table/src/buildQuery.ts @@ -198,11 +198,6 @@ const buildQuery: BuildQuery = ( (ownState.currentPage ?? 0) * (ownState.pageSize ?? 0); } - if (!temporalColumn) { - // This query is not using temporal column, so it doesn't need time grain - extras.time_grain_sqla = undefined; - } - let queryObject = { ...baseQueryObject, columns, diff --git a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx index c051bdff7875..ee3c43a0ec27 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx @@ -326,6 +326,26 @@ const config: ControlPanelConfig = { }, }, ], + [ + { + name: 'order_desc', + config: { + type: 'CheckboxControl', + label: t('Sort descending'), + default: true, + description: t( + 'If enabled, this control sorts the results/values descending, otherwise it sorts the results ascending.', + ), + visibility: ({ controls }: ControlPanelsContainerProps) => { + const hasSortMetric = Boolean( + controls?.timeseries_limit_metric?.value, + ); + return hasSortMetric && isAggMode({ controls }); + }, + resetOnHide: false, + }, + }, + ], [ { name: 'server_pagination', @@ -362,21 +382,6 @@ const config: ControlPanelConfig = { }, }, ], - [ - { - name: 'order_desc', - config: { - type: 'CheckboxControl', - label: t('Sort descending'), - default: true, - description: t( - 'If enabled, this control sorts the results/values descending, otherwise it sorts the results ascending.', - ), - visibility: isAggMode, - resetOnHide: false, - }, - }, - ], [ { name: 'show_totals', @@ -467,7 +472,7 @@ const config: ControlPanelConfig = { renderTrigger: true, default: true, description: t( - 'Renders table cells as HTML when applicable. For example, HTML <a> tags will be rendered as hyperlinks.', + 'Renders table cells as HTML when applicable. For example, HTML
tags will be rendered as hyperlinks.', ), }, }, @@ -486,8 +491,9 @@ const config: ControlPanelConfig = { return true; }, mapStateToProps(explore, _, chart) { - const timeComparisonStatus = - !!explore?.controls?.time_compare?.value; + const timeComparisonStatus = !isEmpty( + explore?.controls?.time_compare?.value, + ); const { colnames: _colnames, coltypes: _coltypes } = chart?.queriesResponse?.[0] ?? {}; @@ -653,7 +659,7 @@ const config: ControlPanelConfig = { value: colname, label: Array.isArray(verboseMap) ? colname - : verboseMap[colname], + : (verboseMap[colname] ?? colname), })) : []; const columnOptions = explore?.controls?.time_compare?.value diff --git a/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts b/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts index 48871e4ea418..d62d9cb92c95 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts @@ -347,6 +347,7 @@ const processComparisonColumns = ( } = props; const savedFormat = columnFormats?.[col.key]; const savedCurrency = currencyFormats?.[col.key]; + const originalLabel = col.label; if ( (col.isMetric || col.isPercentMetric) && !col.key.includes(comparisonSuffix) && @@ -355,6 +356,7 @@ const processComparisonColumns = ( return [ { ...col, + originalLabel, label: t('Main'), key: `${t('Main')} ${col.key}`, config: getComparisonColConfig(t('Main'), col.key, columnConfig), @@ -368,6 +370,7 @@ const processComparisonColumns = ( }, { ...col, + originalLabel, label: `#`, key: `# ${col.key}`, config: getComparisonColConfig(`#`, col.key, columnConfig), @@ -381,6 +384,7 @@ const processComparisonColumns = ( }, { ...col, + originalLabel, label: `△`, key: `△ ${col.key}`, config: getComparisonColConfig(`△`, col.key, columnConfig), @@ -394,6 +398,7 @@ const processComparisonColumns = ( }, { ...col, + originalLabel, label: `%`, key: `% ${col.key}`, config: getComparisonColConfig(`%`, col.key, columnConfig), diff --git a/superset-frontend/plugins/plugin-chart-table/src/types.ts b/superset-frontend/plugins/plugin-chart-table/src/types.ts index 1ec3cbe29d72..62a666a88e7d 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/types.ts +++ b/superset-frontend/plugins/plugin-chart-table/src/types.ts @@ -56,6 +56,8 @@ export interface DataColumnMeta { key: string; // `label` is verbose column name used for rendering label: string; + // `originalLabel` preserves the original label when time comparison transforms the labels + originalLabel?: string; dataType: GenericDataType; formatter?: | TimeFormatter diff --git a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx index b21a657b8150..b74e1ffccf4a 100644 --- a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx +++ b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx @@ -175,6 +175,75 @@ describe('plugin-chart-table', () => { ?.formatter?.(0.123456); expect(formattedPercentMetric).toBe('0.123'); }); + + it('should set originalLabel for comparison columns when time_compare and comparison_type are set', () => { + const transformedProps = transformProps(testData.comparison); + + // Check if comparison columns are processed + const comparisonColumns = transformedProps.columns.filter( + col => + col.label === 'Main' || + col.label === '#' || + col.label === '△' || + col.label === '%', + ); + + expect(comparisonColumns.length).toBeGreaterThan(0); + expect(comparisonColumns.some(col => col.label === 'Main')).toBe(true); + expect(comparisonColumns.some(col => col.label === '#')).toBe(true); + expect(comparisonColumns.some(col => col.label === '△')).toBe(true); + expect(comparisonColumns.some(col => col.label === '%')).toBe(true); + + // Verify originalLabel for metric_1 comparison columns + const mainMetric1 = transformedProps.columns.find( + col => col.key === 'Main metric_1', + ); + expect(mainMetric1).toBeDefined(); + expect(mainMetric1?.originalLabel).toBe('metric_1'); + + const hashMetric1 = transformedProps.columns.find( + col => col.key === '# metric_1', + ); + expect(hashMetric1).toBeDefined(); + expect(hashMetric1?.originalLabel).toBe('metric_1'); + + const deltaMetric1 = transformedProps.columns.find( + col => col.key === '△ metric_1', + ); + expect(deltaMetric1).toBeDefined(); + expect(deltaMetric1?.originalLabel).toBe('metric_1'); + + const percentMetric1 = transformedProps.columns.find( + col => col.key === '% metric_1', + ); + expect(percentMetric1).toBeDefined(); + expect(percentMetric1?.originalLabel).toBe('metric_1'); + + // Verify originalLabel for metric_2 comparison columns + const mainMetric2 = transformedProps.columns.find( + col => col.key === 'Main metric_2', + ); + expect(mainMetric2).toBeDefined(); + expect(mainMetric2?.originalLabel).toBe('metric_2'); + + const hashMetric2 = transformedProps.columns.find( + col => col.key === '# metric_2', + ); + expect(hashMetric2).toBeDefined(); + expect(hashMetric2?.originalLabel).toBe('metric_2'); + + const deltaMetric2 = transformedProps.columns.find( + col => col.key === '△ metric_2', + ); + expect(deltaMetric2).toBeDefined(); + expect(deltaMetric2?.originalLabel).toBe('metric_2'); + + const percentMetric2 = transformedProps.columns.find( + col => col.key === '% metric_2', + ); + expect(percentMetric2).toBeDefined(); + expect(percentMetric2?.originalLabel).toBe('metric_2'); + }); }); describe('TableChart', () => { @@ -400,6 +469,17 @@ describe('plugin-chart-table', () => { ); expect(getComputedStyle(screen.getByText('N/A')).background).toBe(''); }); + it('should display originalLabel in grouped headers', () => { + render( + + + , + ); + + const groupHeaders = screen.getAllByRole('columnheader'); + expect(groupHeaders[0]).toHaveTextContent('metric_1'); + expect(groupHeaders[1]).toHaveTextContent('metric_2'); + }); }); it('render cell bars properly, and only when it is toggled on in both regular and percent metrics', () => { diff --git a/superset-frontend/plugins/plugin-chart-table/test/buildQuery.test.ts b/superset-frontend/plugins/plugin-chart-table/test/buildQuery.test.ts index f110b424c9bb..4badcc673ace 100644 --- a/superset-frontend/plugins/plugin-chart-table/test/buildQuery.test.ts +++ b/superset-frontend/plugins/plugin-chart-table/test/buildQuery.test.ts @@ -148,14 +148,5 @@ describe('plugin-chart-table', () => { expect(queries[1].extras?.time_grain_sqla).toEqual(TimeGranularity.MONTH); expect(queries[1].extras?.where).toEqual("(status IN ('In Process'))"); }); - it('should not include time_grain_sqla in extras if temporal colum is not used and keep the rest', () => { - const { queries } = buildQuery(extraQueryFormData); - // Extras in regular query - expect(queries[0].extras?.time_grain_sqla).toBeUndefined(); - expect(queries[0].extras?.where).toEqual("(status IN ('In Process'))"); - // Extras in summary query - expect(queries[1].extras?.time_grain_sqla).toBeUndefined(); - expect(queries[1].extras?.where).toEqual("(status IN ('In Process'))"); - }); }); }); diff --git a/superset-frontend/spec/fixtures/mockDatabases.ts b/superset-frontend/spec/fixtures/mockDatabases.ts new file mode 100644 index 000000000000..8d3edc3783c3 --- /dev/null +++ b/superset-frontend/spec/fixtures/mockDatabases.ts @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default { + 1: { + allow_ctas: false, + allow_cvas: false, + allow_dml: false, + allow_file_upload: false, + allow_run_async: true, + backend: 'postgresql', + database_name: 'examples', + expose_in_sqllab: true, + force_ctas_schema: null, + id: 1, + }, +}; + +export const disabledAsyncDb = { + 21: { + allow_ctas: false, + allow_cvas: false, + allow_dml: false, + allow_file_upload: false, + allow_run_async: false, + backend: 'postgresql', + database_name: 'examples', + expose_in_sqllab: true, + force_ctas_schema: null, + id: 21, + }, +}; diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.js b/superset-frontend/src/SqlLab/actions/sqlLab.js index 9f62d781ed3b..8b54319a568d 100644 --- a/superset-frontend/src/SqlLab/actions/sqlLab.js +++ b/superset-frontend/src/SqlLab/actions/sqlLab.js @@ -252,28 +252,30 @@ export function querySuccess(query, results) { return { type: QUERY_SUCCESS, query, results }; } -export function queryFailed(query, msg, link, errors) { +export function logFailedQuery(query, errors) { return function (dispatch) { const eventData = { has_err: true, start_offset: query.startDttm, ts: new Date().getTime(), }; - errors?.forEach(({ error_type: errorType, extra }) => { - const messages = extra?.issue_codes?.map(({ message }) => message) || [ - errorType, - ]; - messages.forEach(message => { - dispatch( - logEvent(LOG_ACTIONS_SQLLAB_FETCH_FAILED_QUERY, { - ...eventData, - error_type: errorType, - error_details: message, - }), - ); - }); + errors?.forEach(({ error_type: errorType, message, extra }) => { + const issueCodes = extra?.issue_codes?.map(({ code }) => code) || [-1]; + dispatch( + logEvent(LOG_ACTIONS_SQLLAB_FETCH_FAILED_QUERY, { + ...eventData, + error_type: errorType, + issue_codes: issueCodes, + error_details: message, + }), + ); }); + }; +} +export function queryFailed(query, msg, link, errors) { + return function (dispatch) { + dispatch(logFailedQuery(query, errors)); dispatch({ type: QUERY_FAILED, query, msg, link, errors }); }; } diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.test.js b/superset-frontend/src/SqlLab/actions/sqlLab.test.js index abbdb0c99ef6..d01bfd1b1cbf 100644 --- a/superset-frontend/src/SqlLab/actions/sqlLab.test.js +++ b/superset-frontend/src/SqlLab/actions/sqlLab.test.js @@ -294,7 +294,7 @@ describe('async actions', () => { }); it('calls queryFailed on fetch error and logs the error details', () => { - expect.assertions(3); + expect.assertions(2); fetchMock.post( runQueryEndpoint, @@ -312,7 +312,6 @@ describe('async actions', () => { const expectedActionTypes = [ actions.START_QUERY, LOG_EVENT, - LOG_EVENT, actions.QUERY_FAILED, ]; const { dispatch } = store; @@ -320,12 +319,7 @@ describe('async actions', () => { return request(dispatch, () => initialState).then(() => { const actions = store.getActions(); expect(actions.map(a => a.type)).toEqual(expectedActionTypes); - expect(actions[1].payload.eventData.error_details).toContain( - 'Issue 1000', - ); - expect(actions[2].payload.eventData.error_details).toContain( - 'Issue 1001', - ); + expect(actions[1].payload.eventData.issue_codes).toEqual([1000, 1001]); }); }); }); diff --git a/superset-frontend/src/SqlLab/components/QueryAutoRefresh/QueryAutoRefresh.test.tsx b/superset-frontend/src/SqlLab/components/QueryAutoRefresh/QueryAutoRefresh.test.tsx index 68353ad1f5a8..7a35adccc404 100644 --- a/superset-frontend/src/SqlLab/components/QueryAutoRefresh/QueryAutoRefresh.test.tsx +++ b/superset-frontend/src/SqlLab/components/QueryAutoRefresh/QueryAutoRefresh.test.tsx @@ -16,10 +16,12 @@ * specific language governing permissions and limitations * under the License. */ +import { QueryState } from '@superset-ui/core'; import fetchMock from 'fetch-mock'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { render, waitFor } from 'spec/helpers/testing-library'; +import { LOG_ACTIONS_SQLLAB_FETCH_FAILED_QUERY } from 'src/logger/LogUtils'; import { CLEAR_INACTIVE_QUERIES, REFRESH_QUERIES, @@ -31,9 +33,13 @@ import QueryAutoRefresh, { } from 'src/SqlLab/components/QueryAutoRefresh'; import { successfulQuery, runningQuery } from 'src/SqlLab/fixtures'; import { QueryDictionary } from 'src/SqlLab/types'; +import mockDatabases from 'spec/fixtures/mockDatabases'; const middlewares = [thunk]; const mockStore = configureStore(middlewares); +const mockState = { + databases: mockDatabases, +}; // NOTE: The uses of @ts-ignore in this file is to enable testing of bad inputs to verify the // function / component handles bad data elegantly @@ -106,7 +112,9 @@ describe('QueryAutoRefresh', () => { }); it('Attempts to refresh when given pending query', async () => { - const store = mockStore(); + const store = mockStore({ + sqlLab: { ...mockState }, + }); fetchMock.get(refreshApi, { result: [ { @@ -135,7 +143,7 @@ describe('QueryAutoRefresh', () => { }); it('Attempts to clear inactive queries when updated queries are empty', async () => { - const store = mockStore(); + const store = mockStore({ sqlLab: { ...mockState } }); fetchMock.get(refreshApi, { result: [], }); @@ -163,7 +171,7 @@ describe('QueryAutoRefresh', () => { }); it('Does not fail and attempts to refresh when given pending query and invalid query', async () => { - const store = mockStore(); + const store = mockStore({ sqlLab: { ...mockState } }); fetchMock.get(refreshApi, { result: [ { @@ -193,7 +201,7 @@ describe('QueryAutoRefresh', () => { }); it('Does NOT Attempt to refresh when given only completed queries', async () => { - const store = mockStore(); + const store = mockStore({ sqlLab: { ...mockState } }); fetchMock.get(refreshApi, { result: [ { @@ -220,4 +228,57 @@ describe('QueryAutoRefresh', () => { ); expect(fetchMock.calls(refreshApi)).toHaveLength(0); }); + + it('logs the failed error for async queries', async () => { + const store = mockStore({ sqlLab: { ...mockState } }); + fetchMock.get(refreshApi, { + result: [ + { + id: runningQuery.id, + dbId: 1, + state: QueryState.Failed, + extra: { + errors: [ + { + error_type: 'TEST_ERROR', + level: 'error', + message: 'Syntax invalid', + extra: { + issue_codes: [ + { + code: 102, + message: 'DB failed', + }, + ], + }, + }, + ], + }, + }, + ], + }); + render( + , + { useRedux: true, store }, + ); + await waitFor( + () => + expect(store.getActions()).toContainEqual( + expect.objectContaining({ + payload: expect.objectContaining({ + eventName: LOG_ACTIONS_SQLLAB_FETCH_FAILED_QUERY, + eventData: expect.objectContaining({ + error_type: 'TEST_ERROR', + error_details: 'Syntax invalid', + issue_codes: [102], + }), + }), + }), + ), + { timeout: QUERY_UPDATE_FREQ + 100 }, + ); + }); }); diff --git a/superset-frontend/src/SqlLab/components/QueryAutoRefresh/index.tsx b/superset-frontend/src/SqlLab/components/QueryAutoRefresh/index.tsx index ca9906a75f2d..c7de1208b536 100644 --- a/superset-frontend/src/SqlLab/components/QueryAutoRefresh/index.tsx +++ b/superset-frontend/src/SqlLab/components/QueryAutoRefresh/index.tsx @@ -17,7 +17,7 @@ * under the License. */ import { useRef } from 'react'; -import { useDispatch } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; import { isObject } from 'lodash'; import rison from 'rison'; import { @@ -25,13 +25,17 @@ import { Query, runningQueryStateList, QueryResponse, + QueryState, + lruCache, } from '@superset-ui/core'; -import { QueryDictionary } from 'src/SqlLab/types'; +import { QueryDictionary, SqlLabRootState } from 'src/SqlLab/types'; import useInterval from 'src/SqlLab/utils/useInterval'; import { refreshQueries, clearInactiveQueries, + logFailedQuery, } from 'src/SqlLab/actions/sqlLab'; +import type { DatabaseObject } from 'src/features/databases/types'; export const QUERY_UPDATE_FREQ = 2000; const QUERY_UPDATE_BUFFER_MS = 5000; @@ -67,6 +71,17 @@ function QueryAutoRefresh({ // pendingRequest check ensures we only have one active http call to check for query statuses const pendingRequestRef = useRef(false); const cleanInactiveRequestRef = useRef(false); + const failedQueries = useRef(lruCache(1000)); + const databases = useSelector( + ({ sqlLab }) => sqlLab.databases, + ) as Record; + const asyncFetchDbs = useRef( + new Set( + Object.values(databases) + .filter(({ allow_run_async }) => Boolean(allow_run_async)) + .map(({ id }) => id), + ), + ); const dispatch = useDispatch(); const checkForRefresh = () => { @@ -97,6 +112,17 @@ function QueryAutoRefresh({ {}, ) ?? {}; dispatch(refreshQueries(queries)); + jsonPayload.result.forEach(query => { + const { id, dbId, state } = query; + if ( + asyncFetchDbs.current.has(dbId) && + !failedQueries.current.has(id) && + state === QueryState.Failed + ) { + dispatch(logFailedQuery(query, query.extra?.errors)); + failedQueries.current.set(id, true); + } + }); } else { dispatch(clearInactiveQueries(QUERY_UPDATE_FREQ)); } diff --git a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx index db44fdda164b..ff1c4b772719 100644 --- a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx +++ b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx @@ -457,9 +457,8 @@ const ResultSet = ({
setAlertIsOpen(false)} - description={t( + message={t( 'The number of rows displayed is limited to %(rows)d by the dropdown.', { rows }, )} @@ -471,8 +470,7 @@ const ResultSet = ({ setAlertIsOpen(false)} - message={t('%(rows)d rows returned', { rows: rowsCount })} - description={ + message={ isAdmin ? displayMaxRowsReachedMessage.withAdmin : displayMaxRowsReachedMessage.withoutAdmin diff --git a/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx b/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx index 379b0726ac73..ab17556a72ab 100644 --- a/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx +++ b/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx @@ -96,32 +96,36 @@ interface SaveDatasetModalProps { } const Styles = styled.div` + ${({ theme }) => ` .sdm-body { - margin: 0 8px; + margin: 0 ${theme.gridUnit * 2}px; } .sdm-input { - margin-left: 45px; + margin-left: ${theme.gridUnit * 10}px; width: 401px; } .sdm-autocomplete { width: 401px; align-self: center; + margin-left: ${theme.gridUnit}px; } .sdm-radio { - display: block; height: 30px; margin: 10px 0px; line-height: 30px; } + .sdm-radio span { + display: inline-flex; + padding-right: 0px; + } .sdm-overwrite-msg { - margin: 7px; + margin: ${theme.gridUnit * 2}px; } .sdm-overwrite-container { flex: 1 1 auto; display: flex; - } + `} `; - const updateDataset = async ( dbId: number, datasetId: number, diff --git a/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.test.tsx b/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.test.tsx index 0d30c46f1e91..dbfa9192ece7 100644 --- a/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.test.tsx +++ b/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.test.tsx @@ -16,12 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import { render } from 'spec/helpers/testing-library'; +import { render, waitFor, within } from 'spec/helpers/testing-library'; import SouthPane from 'src/SqlLab/components/SouthPane'; import '@testing-library/jest-dom'; import { STATUS_OPTIONS } from 'src/SqlLab/constants'; import { initialState, table, defaultQueryEditor } from 'src/SqlLab/fixtures'; import { denormalizeTimestamp } from '@superset-ui/core'; +import userEvent from '@testing-library/user-event'; const mockedProps = { queryEditorId: defaultQueryEditor.id, @@ -49,12 +50,14 @@ const mockState = { tables: [ { ...table, + id: 't3', name: 'table3', dataPreviewQueryId: '2g2_iRFMl', queryEditorId: defaultQueryEditor.id, }, { ...table, + id: 't4', name: 'table4', dataPreviewQueryId: 'erWdqEWPm', queryEditorId: defaultQueryEditor.id, @@ -149,3 +152,22 @@ test('should render tabs for table metadata view', () => { expect(tabs[index + 2]).toHaveTextContent(`${schema}.${name}`); }); }); + +test('should remove tab', async () => { + const { getAllByRole } = await render(, { + useRedux: true, + initialState: mockState, + }); + + const tabs = getAllByRole('tab'); + const totalTabs = mockState.sqlLab.tables.length + 2; + expect(tabs).toHaveLength(totalTabs); + const removeButton = within(tabs[2].parentElement as HTMLElement).getByRole( + 'button', + { + name: /remove/, + }, + ); + userEvent.click(removeButton); + await waitFor(() => expect(getAllByRole('tab')).toHaveLength(totalTabs - 1)); +}); diff --git a/superset-frontend/src/SqlLab/components/SouthPane/index.tsx b/superset-frontend/src/SqlLab/components/SouthPane/index.tsx index dbde83ae9d69..fe929cb08010 100644 --- a/superset-frontend/src/SqlLab/components/SouthPane/index.tsx +++ b/superset-frontend/src/SqlLab/components/SouthPane/index.tsx @@ -136,7 +136,7 @@ const SouthPane = ({ dispatch(removeTables([table])); } }, - [dispatch, queryEditorId], + [dispatch, pinnedTables], ); return offline ? ( diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx index 527c22752d01..5b2204067285 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx @@ -324,6 +324,8 @@ const SqlEditor: FC = ({ const SqlFormExtension = extensionsRegistry.get('sqleditor.extension.form'); + const isTempId = (value: unknown): boolean => Number.isNaN(Number(value)); + const startQuery = useCallback( (ctasArg = false, ctas_method = CtasEnum.Table) => { if (!database) { @@ -909,7 +911,7 @@ const SqlEditor: FC = ({ )} {isActive && ( { success: 'primary', secondary: 'default', default: 'default', - tertiary: 'dashed', + tertiary: 'default', dashed: 'dashed', link: 'link', }; diff --git a/superset-frontend/src/components/CachedLabel/index.tsx b/superset-frontend/src/components/CachedLabel/index.tsx index e237c3a1645c..67f40e9d5685 100644 --- a/superset-frontend/src/components/CachedLabel/index.tsx +++ b/superset-frontend/src/components/CachedLabel/index.tsx @@ -18,10 +18,11 @@ */ import { useState, MouseEventHandler, FC } from 'react'; -import { t } from '@superset-ui/core'; +import { css, t } from '@superset-ui/core'; import Label from 'src/components/Label'; import { Tooltip } from 'src/components/Tooltip'; import { TooltipContent } from './TooltipContent'; +import Icons from '../Icons'; export interface CacheLabelProps { onClick?: MouseEventHandler; @@ -44,12 +45,16 @@ const CacheLabel: FC = ({ > ); diff --git a/superset-frontend/src/components/Chart/Chart.tsx b/superset-frontend/src/components/Chart/Chart.tsx index 0389ebd3094d..94f521bcd1fc 100644 --- a/superset-frontend/src/components/Chart/Chart.tsx +++ b/superset-frontend/src/components/Chart/Chart.tsx @@ -24,7 +24,6 @@ import { logging, QueryFormData, styled, - ErrorTypeEnum, t, SqlaFormData, ClientErrorObject, @@ -240,15 +239,7 @@ class Chart extends PureComponent { height, datasetsStatus, } = this.props; - let error = queryResponse?.errors?.[0]; - if (error === undefined) { - error = { - error_type: ErrorTypeEnum.FRONTEND_NETWORK_ERROR, - level: 'error', - message: t('Check your network connection'), - extra: null, - }; - } + const error = queryResponse?.errors?.[0]; const message = chartAlert || queryResponse?.message; // if datasource is still loading, don't render JS errors diff --git a/superset-frontend/src/components/Chart/ChartErrorMessage.test.tsx b/superset-frontend/src/components/Chart/ChartErrorMessage.test.tsx new file mode 100644 index 000000000000..6d05a7b9df1e --- /dev/null +++ b/superset-frontend/src/components/Chart/ChartErrorMessage.test.tsx @@ -0,0 +1,84 @@ +/** + * 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 { render, screen } from 'spec/helpers/testing-library'; +import '@testing-library/jest-dom'; +import { ChartSource } from 'src/types/ChartSource'; +import { useChartOwnerNames } from 'src/hooks/apiResources'; +import { ResourceStatus } from 'src/hooks/apiResources/apiResources'; +import { ErrorType } from '@superset-ui/core'; +import { ChartErrorMessage } from './ChartErrorMessage'; +import { ErrorMessageComponentProps } from '../ErrorMessage/types'; +import getErrorMessageComponentRegistry from '../ErrorMessage/getErrorMessageComponentRegistry'; + +// Mock the useChartOwnerNames hook +jest.mock('src/hooks/apiResources', () => ({ + useChartOwnerNames: jest.fn(), +})); + +const mockUseChartOwnerNames = useChartOwnerNames as jest.MockedFunction< + typeof useChartOwnerNames +>; + +const ERROR_MESSAGE_COMPONENT = (props: ErrorMessageComponentProps) => ( + <> +
Test error
+
{props.subtitle}
+ +); + +describe('ChartErrorMessage', () => { + const defaultProps = { + chartId: '1', + subtitle: 'Test subtitle', + source: 'test_source' as ChartSource, + }; + + it('renders the default error message when error is null', () => { + mockUseChartOwnerNames.mockReturnValue({ + result: null, + status: ResourceStatus.Loading, + error: null, + }); + render(); + + expect(screen.getByText('Data error')).toBeInTheDocument(); + expect(screen.getByText('Test subtitle')).toBeInTheDocument(); + }); + + it('renders the error message that is passed in from the error', () => { + getErrorMessageComponentRegistry().registerValue( + 'VALID_KEY', + ERROR_MESSAGE_COMPONENT, + ); + render( + , + ); + + expect(screen.getByText('Test error')).toBeInTheDocument(); + expect(screen.getByText('Test subtitle')).toBeInTheDocument(); + }); +}); diff --git a/superset-frontend/src/components/Chart/ChartErrorMessage.tsx b/superset-frontend/src/components/Chart/ChartErrorMessage.tsx index 0454f1fe464d..b6d3a851063f 100644 --- a/superset-frontend/src/components/Chart/ChartErrorMessage.tsx +++ b/superset-frontend/src/components/Chart/ChartErrorMessage.tsx @@ -32,6 +32,8 @@ export type Props = { stackTrace?: string; } & Omit; +const DEFAULT_CHART_ERROR = 'Data error'; + export const ChartErrorMessage: FC = ({ chartId, error, ...props }) => { // fetches the chart owners and adds them to the extra data of the error message const { result: owners } = useChartOwnerNames(chartId); @@ -42,5 +44,11 @@ export const ChartErrorMessage: FC = ({ chartId, error, ...props }) => { extra: { ...error.extra, owners }, }; - return ; + return ( + + ); }; diff --git a/superset-frontend/src/components/Chart/ChartRenderer.jsx b/superset-frontend/src/components/Chart/ChartRenderer.jsx index 25fc045d2105..3707269ee1ab 100644 --- a/superset-frontend/src/components/Chart/ChartRenderer.jsx +++ b/superset-frontend/src/components/Chart/ChartRenderer.jsx @@ -95,6 +95,7 @@ class ChartRenderer extends Component { isFeatureEnabled(FeatureFlag.DrillToDetail), inContextMenu: false, legendState: undefined, + legendIndex: 0, }; this.hasQueryResponseChange = false; @@ -109,6 +110,7 @@ class ChartRenderer extends Component { this.handleContextMenuClosed = this.handleContextMenuClosed.bind(this); this.handleLegendStateChanged = this.handleLegendStateChanged.bind(this); this.onContextMenuFallback = this.onContextMenuFallback.bind(this); + this.handleLegendScroll = this.handleLegendScroll.bind(this); this.hooks = { onAddFilter: this.handleAddFilter, @@ -123,6 +125,7 @@ class ChartRenderer extends Component { setDataMask: dataMask => { this.props.actions?.updateDataMask(this.props.chartId, dataMask); }, + onLegendScroll: this.handleLegendScroll, }; // TODO: queriesResponse comes from Redux store but it's being edited by @@ -246,6 +249,10 @@ class ChartRenderer extends Component { } } + handleLegendScroll(legendIndex) { + this.setState({ legendIndex }); + } + render() { const { chartAlert, chartStatus, chartId, emitCrossFilters } = this.props; @@ -367,6 +374,7 @@ class ChartRenderer extends Component { postTransformProps={postTransformProps} emitCrossFilters={emitCrossFilters} legendState={this.state.legendState} + legendIndex={this.state.legendIndex} {...drillToDetailProps} />
diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx index 3b677316a47e..96223b105fe8 100644 --- a/superset-frontend/src/components/DatabaseSelector/index.tsx +++ b/superset-frontend/src/components/DatabaseSelector/index.tsx @@ -406,6 +406,7 @@ export default function DatabaseSelector({ options={catalogOptions} showSearch value={currentCatalog || undefined} + allowClear />, refreshIcon, ); @@ -432,6 +433,7 @@ export default function DatabaseSelector({ options={schemaOptions} showSearch value={currentSchema} + allowClear />, refreshIcon, ); diff --git a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx index d30b67ad2a75..ceb832d75053 100644 --- a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx +++ b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx @@ -23,7 +23,6 @@ import { Radio } from 'src/components/Radio'; import Card from 'src/components/Card'; import Alert from 'src/components/Alert'; import Badge from 'src/components/Badge'; -import { nanoid } from 'nanoid'; import { css, isFeatureEnabled, @@ -57,6 +56,7 @@ import CurrencyControl from 'src/explore/components/controls/CurrencyControl'; import CollectionTable from './CollectionTable'; import Fieldset from './Fieldset'; import Field from './Field'; +import { fetchSyncedColumns, updateColumns } from './utils'; const DatasourceContainer = styled.div` .change-warning { @@ -140,6 +140,14 @@ const StyledButtonWrapper = styled.span` `} `; +const sqlTooltipOptions = { + placement: 'topRight', + title: t( + 'If changes are made to your SQL query, ' + + 'columns in your dataset will be synced when saving the dataset.', + ), +}; + const checkboxGenerator = (d, onChange) => ( ); @@ -694,116 +702,31 @@ class DatasourceEditor extends PureComponent { }); } - updateColumns(cols) { - // cols: Array<{column_name: string; is_dttm: boolean; type: string;}> - const { databaseColumns } = this.state; - const databaseColumnNames = cols.map(col => col.column_name); - const currentCols = databaseColumns.reduce( - (agg, col) => ({ - ...agg, - [col.column_name]: col, - }), - {}, - ); - const finalColumns = []; - const results = { - added: [], - modified: [], - removed: databaseColumns - .map(col => col.column_name) - .filter(col => !databaseColumnNames.includes(col)), - }; - cols.forEach(col => { - const currentCol = currentCols[col.column_name]; - if (!currentCol) { - // new column - finalColumns.push({ - id: nanoid(), - column_name: col.column_name, - type: col.type, - groupby: true, - filterable: true, - is_dttm: col.is_dttm, - }); - results.added.push(col.column_name); - } else if ( - currentCol.type !== col.type || - (!currentCol.is_dttm && col.is_dttm) - ) { - // modified column - finalColumns.push({ - ...currentCol, - type: col.type, - is_dttm: currentCol.is_dttm || col.is_dttm, - }); - results.modified.push(col.column_name); - } else { - // unchanged - finalColumns.push(currentCol); - } - }); - if ( - results.added.length || - results.modified.length || - results.removed.length - ) { - this.setColumns({ databaseColumns: finalColumns }); - } - return results; - } - - syncMetadata() { + async syncMetadata() { const { datasource } = this.state; - const params = { - datasource_type: datasource.type || datasource.datasource_type, - database_name: - datasource.database.database_name || datasource.database.name, - catalog_name: datasource.catalog, - schema_name: datasource.schema, - table_name: datasource.table_name, - normalize_columns: datasource.normalize_columns, - always_filter_main_dttm: datasource.always_filter_main_dttm, - }; - Object.entries(params).forEach(([key, value]) => { - // rison can't encode the undefined value - if (value === undefined) { - params[key] = null; - } - }); - const endpoint = `/datasource/external_metadata_by_name/?q=${rison.encode_uri( - params, - )}`; this.setState({ metadataLoading: true }); - - SupersetClient.get({ endpoint }) - .then(({ json }) => { - const results = this.updateColumns(json); - if (results.modified.length) { - this.props.addSuccessToast( - t('Modified columns: %s', results.modified.join(', ')), - ); - } - if (results.removed.length) { - this.props.addSuccessToast( - t('Removed columns: %s', results.removed.join(', ')), - ); - } - if (results.added.length) { - this.props.addSuccessToast( - t('New columns added: %s', results.added.join(', ')), - ); - } - this.props.addSuccessToast(t('Metadata has been synced')); - this.setState({ metadataLoading: false }); - }) - .catch(response => - getClientErrorObject(response).then(({ error, statusText }) => { - this.props.addDangerToast( - error || statusText || t('An error has occurred'), - ); - this.setState({ metadataLoading: false }); - }), + try { + const newCols = await fetchSyncedColumns(datasource); + const columnChanges = updateColumns( + datasource.columns, + newCols, + this.props.addSuccessToast, ); + this.setColumns({ + databaseColumns: columnChanges.finalColumns.filter( + col => !col.expression, // remove calculated columns + ), + }); + this.props.addSuccessToast(t('Metadata has been synced')); + this.setState({ metadataLoading: false }); + } catch (error) { + const { error: clientError, statusText } = + await getClientErrorObject(error); + this.props.addDangerToast( + clientError || statusText || t('An error has occurred'), + ); + this.setState({ metadataLoading: false }); + } } findDuplicates(arr, accessor) { @@ -1146,6 +1069,7 @@ class DatasourceEditor extends PureComponent { maxLines={Infinity} readOnly={!this.state.isEditMode} resize="both" + tooltipOptions={sqlTooltipOptions} /> } /> diff --git a/superset-frontend/src/components/Datasource/DatasourceModal.tsx b/superset-frontend/src/components/Datasource/DatasourceModal.tsx index 78483771d340..33cd820677e4 100644 --- a/superset-frontend/src/components/Datasource/DatasourceModal.tsx +++ b/superset-frontend/src/components/Datasource/DatasourceModal.tsx @@ -17,11 +17,11 @@ * under the License. */ import { FunctionComponent, useState, useRef } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import Alert from 'src/components/Alert'; import Button from 'src/components/Button'; import { isDefined, - Metric, styled, SupersetClient, getClientErrorObject, @@ -33,7 +33,16 @@ import Modal from 'src/components/Modal'; import AsyncEsmComponent from 'src/components/AsyncEsmComponent'; import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace'; import withToasts from 'src/components/MessageToasts/withToasts'; -import { useSelector } from 'react-redux'; +import { + startMetaDataLoading, + stopMetaDataLoading, + syncDatasourceMetadata, +} from 'src/explore/actions/exploreActions'; +import { + fetchSyncedColumns, + updateColumns, +} from 'src/components/Datasource/utils'; +import { DatasetObject } from '../../features/datasets/types'; const DatasourceEditor = AsyncEsmComponent(() => import('./DatasourceEditor')); @@ -60,14 +69,17 @@ const StyledDatasourceModal = styled(Modal)` interface DatasourceModalProps { addSuccessToast: (msg: string) => void; - datasource: any; + addDangerToast: (msg: string) => void; + datasource: DatasetObject; onChange: () => {}; onDatasourceSave: (datasource: object, errors?: Array) => {}; onHide: () => {}; show: boolean; } -function buildExtraJsonObject(item: Record) { +function buildExtraJsonObject( + item: DatasetObject['metrics'][0] | DatasetObject['columns'][0], +) { const certification = item?.certified_by || item?.certification_details ? { @@ -83,18 +95,14 @@ function buildExtraJsonObject(item: Record) { const DatasourceModal: FunctionComponent = ({ addSuccessToast, + addDangerToast, datasource, onDatasourceSave, onHide, show, }) => { - const [currentDatasource, setCurrentDatasource] = useState({ - ...datasource, - metrics: datasource?.metrics?.map((metric: Metric) => ({ - ...metric, - currency: JSON.parse(metric.currency || 'null'), - })), - }); + const dispatch = useDispatch(); + const [currentDatasource, setCurrentDatasource] = useState(datasource); const currencies = useSelector< { common: { @@ -108,130 +116,145 @@ const DatasourceModal: FunctionComponent = ({ const [isEditing, setIsEditing] = useState(false); const dialog = useRef(null); const [modal, contextHolder] = Modal.useModal(); - - const onConfirmSave = () => { + const buildPayload = (datasource: Record) => ({ + table_name: datasource.table_name, + database_id: datasource.database?.id, + sql: datasource.sql, + filter_select_enabled: datasource.filter_select_enabled, + fetch_values_predicate: datasource.fetch_values_predicate, + schema: + datasource.tableSelector?.schema || + datasource.databaseSelector?.schema || + datasource.schema, + description: datasource.description, + main_dttm_col: datasource.main_dttm_col, + normalize_columns: datasource.normalize_columns, + always_filter_main_dttm: datasource.always_filter_main_dttm, + offset: datasource.offset, + default_endpoint: datasource.default_endpoint, + cache_timeout: + datasource.cache_timeout === '' ? null : datasource.cache_timeout, + is_sqllab_view: datasource.is_sqllab_view, + template_params: datasource.template_params, + extra: datasource.extra, + is_managed_externally: datasource.is_managed_externally, + external_url: datasource.external_url, + metrics: datasource?.metrics?.map((metric: DatasetObject['metrics'][0]) => { + const metricBody: any = { + expression: metric.expression, + description: metric.description, + metric_name: metric.metric_name, + metric_type: metric.metric_type, + d3format: metric.d3format || null, + currency: !isDefined(metric.currency) + ? null + : JSON.stringify(metric.currency), + verbose_name: metric.verbose_name, + warning_text: metric.warning_text, + uuid: metric.uuid, + extra: buildExtraJsonObject(metric), + }; + if (!Number.isNaN(Number(metric.id))) { + metricBody.id = metric.id; + } + return metricBody; + }), + columns: datasource?.columns?.map( + (column: DatasetObject['columns'][0]) => ({ + id: typeof column.id === 'number' ? column.id : undefined, + column_name: column.column_name, + type: column.type, + advanced_data_type: column.advanced_data_type, + verbose_name: column.verbose_name, + description: column.description, + expression: column.expression, + filterable: column.filterable, + groupby: column.groupby, + is_active: column.is_active, + is_dttm: column.is_dttm, + python_date_format: column.python_date_format || null, + uuid: column.uuid, + extra: buildExtraJsonObject(column), + }), + ), + owners: datasource.owners.map( + (o: Record) => o.value || o.id, + ), + }); + const onConfirmSave = async () => { // Pull out extra fields into the extra object - const schema = - currentDatasource.tableSelector?.schema || - currentDatasource.databaseSelector?.schema || - currentDatasource.schema; - setIsSaving(true); - SupersetClient.put({ - endpoint: `/api/v1/dataset/${currentDatasource.id}`, - jsonPayload: { - table_name: currentDatasource.table_name, - database_id: currentDatasource.database?.id, - sql: currentDatasource.sql, - filter_select_enabled: currentDatasource.filter_select_enabled, - fetch_values_predicate: currentDatasource.fetch_values_predicate, - schema, - description: currentDatasource.description, - main_dttm_col: currentDatasource.main_dttm_col, - normalize_columns: currentDatasource.normalize_columns, - always_filter_main_dttm: currentDatasource.always_filter_main_dttm, - offset: currentDatasource.offset, - default_endpoint: currentDatasource.default_endpoint, - cache_timeout: - currentDatasource.cache_timeout === '' - ? null - : currentDatasource.cache_timeout, - is_sqllab_view: currentDatasource.is_sqllab_view, - template_params: currentDatasource.template_params, - extra: currentDatasource.extra, - is_managed_externally: currentDatasource.is_managed_externally, - external_url: currentDatasource.external_url, - metrics: currentDatasource?.metrics?.map( - (metric: Record) => { - const metricBody: any = { - expression: metric.expression, - description: metric.description, - metric_name: metric.metric_name, - metric_type: metric.metric_type, - d3format: metric.d3format || null, - currency: !isDefined(metric.currency) - ? null - : JSON.stringify(metric.currency), - verbose_name: metric.verbose_name, - warning_text: metric.warning_text, - uuid: metric.uuid, - extra: buildExtraJsonObject(metric), - }; - if (!Number.isNaN(Number(metric.id))) { - metricBody.id = metric.id; - } - return metricBody; - }, - ), - columns: currentDatasource?.columns?.map( - (column: Record) => ({ - id: typeof column.id === 'number' ? column.id : undefined, - column_name: column.column_name, - type: column.type, - advanced_data_type: column.advanced_data_type, - verbose_name: column.verbose_name, - description: column.description, - expression: column.expression, - filterable: column.filterable, - groupby: column.groupby, - is_active: column.is_active, - is_dttm: column.is_dttm, - python_date_format: column.python_date_format || null, - uuid: column.uuid, - extra: buildExtraJsonObject(column), - }), - ), - owners: currentDatasource.owners.map( - (o: Record) => o.value || o.id, - ), - }, - }) - .then(() => { - addSuccessToast(t('The dataset has been saved')); - return SupersetClient.get({ - endpoint: `/api/v1/dataset/${currentDatasource?.id}`, - }); - }) - .then(({ json }) => { - // eslint-disable-next-line no-param-reassign - json.result.type = 'table'; - onDatasourceSave({ - ...json.result, - owners: currentDatasource.owners, - }); - onHide(); - }) - .catch(response => { - setIsSaving(false); - getClientErrorObject(response).then(error => { - let errorResponse: SupersetError | undefined; - let errorText: string | undefined; - // sip-40 error response - if (error?.errors?.length) { - errorResponse = error.errors[0]; - } else if (typeof error.error === 'string') { - // backward compatible with old error messages - errorText = error.error; - } - modal.error({ - title: t('Error saving dataset'), - okButtonProps: { danger: true, className: 'btn-danger' }, - content: ( - - ), - }); + try { + await SupersetClient.put({ + endpoint: `/api/v1/dataset/${currentDatasource.id}`, + jsonPayload: buildPayload(currentDatasource), + }); + if (datasource.sql !== currentDatasource.sql) { + // if sql has changed, save a second time with synced columns + dispatch(startMetaDataLoading()); + try { + const columnJson = await fetchSyncedColumns(currentDatasource); + const columnChanges = updateColumns( + currentDatasource.columns, + columnJson, + addSuccessToast, + ); + currentDatasource.columns = columnChanges.finalColumns; + dispatch(syncDatasourceMetadata(currentDatasource)); + dispatch(stopMetaDataLoading()); + addSuccessToast(t('Metadata has been synced')); + } catch (error) { + dispatch(stopMetaDataLoading()); + addDangerToast( + t('An error has occurred while syncing virtual dataset columns'), + ); + } + await SupersetClient.put({ + endpoint: `/api/v1/dataset/${currentDatasource.id}`, + jsonPayload: buildPayload(currentDatasource), }); + } + const { json } = await SupersetClient.get({ + endpoint: `/api/v1/dataset/${currentDatasource?.id}`, + }); + addSuccessToast(t('The dataset has been saved')); + // eslint-disable-next-line no-param-reassign + json.result.type = 'table'; + onDatasourceSave({ + ...json.result, + owners: currentDatasource.owners, + }); + onHide(); + } catch (response) { + setIsSaving(false); + const error = await getClientErrorObject(response); + let errorResponse: SupersetError | undefined; + let errorText: string | undefined; + // sip-40 error response + if (error?.errors?.length) { + errorResponse = error.errors[0]; + } else if (typeof error.error === 'string') { + // backward compatible with old error messages + errorText = error.error; + } + modal.error({ + title: t('Error saving dataset'), + okButtonProps: { danger: true, className: 'btn-danger' }, + content: ( + + ), }); + } }; - const onDatasourceChange = (data: Record, err: Array) => { + const onDatasourceChange = (data: DatasetObject, err: Array) => { setCurrentDatasource({ ...data, - metrics: data?.metrics.map((metric: Record) => ({ + metrics: data?.metrics.map((metric: DatasetObject['metrics'][0]) => ({ ...metric, is_certified: metric?.certified_by || metric?.certification_details, })), diff --git a/superset-frontend/src/components/Datasource/utils.js b/superset-frontend/src/components/Datasource/utils.js index ccdb1b414a09..11c0d45a2193 100644 --- a/superset-frontend/src/components/Datasource/utils.js +++ b/superset-frontend/src/components/Datasource/utils.js @@ -17,6 +17,9 @@ * under the License. */ import { Children, cloneElement } from 'react'; +import { nanoid } from 'nanoid'; +import { SupersetClient, tn } from '@superset-ui/core'; +import rison from 'rison'; export function recurseReactClone(children, type, propExtender) { /** @@ -40,3 +43,115 @@ export function recurseReactClone(children, type, propExtender) { return newChild; }); } + +export function updateColumns(prevCols, newCols, addSuccessToast) { + // cols: Array<{column_name: string; is_dttm: boolean; type: string;}> + const databaseColumnNames = newCols.map(col => col.column_name); + const currentCols = prevCols.reduce((agg, col) => { + // eslint-disable-next-line no-param-reassign + agg[col.column_name] = col; + return agg; + }, {}); + const columnChanges = { + added: [], + modified: [], + removed: prevCols + .filter( + col => + !(col.expression || databaseColumnNames.includes(col.column_name)), + ) + .map(col => col.column_name), + finalColumns: [], + }; + newCols.forEach(col => { + const currentCol = currentCols[col.column_name]; + if (!currentCol) { + // new column + columnChanges.finalColumns.push({ + id: nanoid(), + column_name: col.column_name, + type: col.type, + groupby: true, + filterable: true, + is_dttm: col.is_dttm, + }); + columnChanges.added.push(col.column_name); + } else if ( + currentCol.type !== col.type || + currentCol.is_dttm !== col.is_dttm + ) { + // modified column + columnChanges.finalColumns.push({ + ...currentCol, + type: col.type, + is_dttm: currentCol.is_dttm || col.is_dttm, + }); + columnChanges.modified.push(col.column_name); + } else { + // unchanged + columnChanges.finalColumns.push(currentCol); + } + }); + // push all calculated columns + prevCols + .filter(col => col.expression) + .forEach(col => { + columnChanges.finalColumns.push(col); + }); + + if (columnChanges.modified.length) { + addSuccessToast( + tn( + 'Modified 1 column in the virtual dataset', + 'Modified %s columns in the virtual dataset', + columnChanges.modified.length, + columnChanges.modified.length, + ), + ); + } + if (columnChanges.removed.length) { + addSuccessToast( + tn( + 'Removed 1 column from the virtual dataset', + 'Removed %s columns from the virtual dataset', + columnChanges.removed.length, + columnChanges.removed.length, + ), + ); + } + if (columnChanges.added.length) { + addSuccessToast( + tn( + 'Added 1 new column to the virtual dataset', + 'Added %s new columns to the virtual dataset', + columnChanges.added.length, + columnChanges.added.length, + ), + ); + } + return columnChanges; +} + +export async function fetchSyncedColumns(datasource) { + const params = { + datasource_type: datasource.type || datasource.datasource_type, + database_name: + datasource.database?.database_name || datasource.database?.name, + catalog_name: datasource.catalog, + schema_name: datasource.schema, + table_name: datasource.table_name, + normalize_columns: datasource.normalize_columns, + always_filter_main_dttm: datasource.always_filter_main_dttm, + }; + Object.entries(params).forEach(([key, value]) => { + // rison can't encode the undefined value + if (value === undefined) { + params[key] = null; + } + }); + const endpoint = `/datasource/external_metadata_by_name/?q=${rison.encode_uri( + params, + )}`; + const { json } = await SupersetClient.get({ endpoint }); + return json; +} diff --git a/superset-frontend/src/components/Datasource/utils.test.tsx b/superset-frontend/src/components/Datasource/utils.test.tsx new file mode 100644 index 000000000000..83b6ca99870e --- /dev/null +++ b/superset-frontend/src/components/Datasource/utils.test.tsx @@ -0,0 +1,209 @@ +/** + * 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 { tn } from '@superset-ui/core'; +import { updateColumns } from './utils'; + +describe('updateColumns', () => { + let addSuccessToast: jest.Mock; + + beforeEach(() => { + addSuccessToast = jest.fn(); + }); + + it('adds new columns when prevCols is empty', () => { + interface Column { + column_name: string; + type: string; + is_dttm: boolean; + } + + const prevCols: Column[] = []; + const newCols = [ + { column_name: 'col1', type: 'string', is_dttm: false }, + { column_name: 'col2', type: 'number', is_dttm: true }, + ]; + const result = updateColumns(prevCols, newCols, addSuccessToast); + expect(result.added.sort()).toEqual(['col1', 'col2'].sort()); + expect(result.modified).toEqual([]); + expect(result.removed).toEqual([]); + expect(result.finalColumns).toHaveLength(2); + // Only the added toast should be fired + expect(addSuccessToast).toHaveBeenCalledTimes(1); + expect(addSuccessToast).toHaveBeenCalledWith( + tn( + 'Added 1 new column to the virtual dataset', + 'Added %s new columns to the virtual dataset', + 2, + 2, + ), + ); + }); + + it('modifies columns when type or is_dttm changes', () => { + const prevCols = [ + { column_name: 'col1', type: 'string', is_dttm: false }, + { column_name: 'col2', type: 'number', is_dttm: false }, + ]; + const newCols = [ + // col1 unchanged + { column_name: 'col1', type: 'string', is_dttm: false }, + // col2 modified (type changed) + { column_name: 'col2', type: 'float', is_dttm: false }, + ]; + const result = updateColumns(prevCols, newCols, addSuccessToast); + expect(result.added).toEqual([]); + expect(result.modified).toEqual(['col2']); + // No columns removed + expect(result.removed).toEqual([]); + // Final columns: first is unchanged, second is updated + expect(result.finalColumns).toHaveLength(2); + const updatedCol2 = ( + result.finalColumns as { + column_name: string; + type: string; + is_dttm: boolean; + }[] + ).find(c => c.column_name === 'col2'); + expect(updatedCol2?.type).toBe('float'); + // Modified toast should be fired + expect(addSuccessToast).toHaveBeenCalledTimes(1); + expect(addSuccessToast).toHaveBeenCalledWith( + tn( + 'Modified 1 column in the virtual dataset', + 'Modified %s columns in the virtual dataset', + 1, + 1, + ), + ); + }); + + it('removes columns not present in newCols', () => { + const prevCols = [ + { column_name: 'col1', type: 'string', is_dttm: false }, + { column_name: 'col2', type: 'number', is_dttm: true }, + ]; + const newCols = [ + // Only col2 is present + { column_name: 'col2', type: 'number', is_dttm: true }, + ]; + const result = updateColumns(prevCols, newCols, addSuccessToast); + // col1 should be marked as removed + expect(result.removed).toEqual(['col1']); + expect(result.added).toEqual([]); + expect(result.modified).toEqual([]); + expect(result.finalColumns).toHaveLength(1); + // Removed toast should be fired + expect(addSuccessToast).toHaveBeenCalledTimes(1); + expect(addSuccessToast).toHaveBeenCalledWith( + tn( + 'Removed 1 column from the virtual dataset', + 'Removed %s columns from the virtual dataset', + 1, + 1, + ), + ); + }); + + it('handles combined additions, modifications, and removals', () => { + const prevCols = [ + { column_name: 'col1', type: 'string', is_dttm: false }, + { column_name: 'col2', type: 'number', is_dttm: false }, + { column_name: 'col3', type: 'number', is_dttm: false }, + ]; + const newCols = [ + // col1 modified + { column_name: 'col1', type: 'string', is_dttm: true }, + // col2 unchanged + { column_name: 'col2', type: 'number', is_dttm: false }, + // col4 is a new column + { column_name: 'col4', type: 'boolean', is_dttm: false }, + ]; + const result = updateColumns(prevCols, newCols, addSuccessToast); + expect(result.added).toEqual(['col4']); + expect(result.modified).toEqual(['col1']); + // col3 is removed since it is missing in newCols and has no expression + expect(result.removed).toEqual(['col3']); + expect(result.finalColumns).toHaveLength(3); + // Three types of changes should fire three separate toasts + expect(addSuccessToast).toHaveBeenCalledTimes(3); + expect(addSuccessToast.mock.calls).toEqual([ + [ + tn( + 'Modified 1 column in the virtual dataset', + 'Modified %s columns in the virtual dataset', + 1, + 1, + ), + ], + [ + tn( + 'Removed 1 column from the virtual dataset', + 'Removed %s columns from the virtual dataset', + 1, + 1, + ), + ], + [ + tn( + 'Added 1 new column to the virtual dataset', + 'Added %s new columns to the virtual dataset', + 1, + 1, + ), + ], + ]); + }); + it('should not remove columns with expressions', () => { + const prevCols = [ + { column_name: 'col1', type: 'string', is_dttm: false }, + { column_name: 'col2', type: 'number', is_dttm: false }, + { + column_name: 'col3', + type: 'number', + is_dttm: false, + expression: 'expr', + }, + ]; + const newCols = [ + // col1 modified + { column_name: 'col1', type: 'string', is_dttm: true }, + // col2 unchanged + { column_name: 'col2', type: 'number', is_dttm: false }, + ]; + const result = updateColumns(prevCols, newCols, addSuccessToast); + expect(result.added).toEqual([]); + expect(result.modified).toEqual(['col1']); + // col3 is not removed since it has an expression + expect(result.removed).toEqual([]); + expect(result.finalColumns).toHaveLength(3); + // Two types of changes should fire two separate toasts + expect(addSuccessToast).toHaveBeenCalledTimes(1); + expect(addSuccessToast.mock.calls).toEqual([ + [ + tn( + 'Modified 1 column in the virtual dataset', + 'Modified %s columns in the virtual dataset', + 1, + 1, + ), + ], + ]); + }); +}); diff --git a/superset-frontend/src/components/DatePicker/index.tsx b/superset-frontend/src/components/DatePicker/index.tsx index ed79c69e9da2..a9b1f465f432 100644 --- a/superset-frontend/src/components/DatePicker/index.tsx +++ b/superset-frontend/src/components/DatePicker/index.tsx @@ -16,9 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -import { DatePicker as AntdDatePicker } from 'antd-v5'; +import { DatePicker as AntdDatePicker, DatePickerProps } from 'antd-v5'; +import { css } from '@superset-ui/core'; -export const DatePicker = AntdDatePicker; +export const DatePicker = (props: DatePickerProps) => ( + +); // Disable ESLint rule to allow tsc to infer proper type for RangePicker. // eslint-disable-next-line prefer-destructuring diff --git a/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx b/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx index 9d364245748b..b993927a7cda 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx @@ -42,7 +42,6 @@ export default function ErrorMessageWithStackTrace({ title = DEFAULT_TITLE, error, subtitle, - copyText, link, stackTrace, source, diff --git a/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.test.tsx b/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.test.tsx index 7db4c862cc36..192be4a0d2d3 100644 --- a/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.test.tsx @@ -43,6 +43,21 @@ const defaultProps = { subtitle: 'Test subtitle', }; +const missingExtraProps = { + ...defaultProps, + error: { + error_type: ErrorTypeEnum.INVALID_SQL_ERROR, + message: 'SQLStatement should have exactly one statement', + level: 'error' as ErrorLevel, + extra: { + sql: null, + line: null, + column: null, + engine: null, + }, + }, +}; + const renderComponent = (overrides = {}) => render( @@ -60,6 +75,12 @@ describe('InvalidSQLErrorMessage', () => { expect(getByText('SELECT * FFROM table')).toBeInTheDocument(); }); + it('renders the error message with the empty extra properties', () => { + const { getByText } = renderComponent(missingExtraProps); + expect(getByText('Unable to parse SQL')).toBeInTheDocument(); + expect(getByText(missingExtraProps.error.message)).toBeInTheDocument(); + }); + it('displays the SQL error line and column indicator', () => { const { getByText, container } = renderComponent(); diff --git a/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.tsx b/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.tsx index 21236e92a029..27ca263cfd09 100644 --- a/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.tsx +++ b/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.tsx @@ -36,20 +36,22 @@ function InvalidSQLErrorMessage({ source, subtitle, }: ErrorMessageComponentProps) { - const { extra, level } = error; + const { extra, level, message } = error; const { sql, line, column } = extra; - const lines = sql.split('\n'); + const lines = sql?.split('\n'); let errorLine; - if (line !== null) errorLine = lines[line - 1]; - else if (lines.length > 0) { + if (line !== null && Number.isInteger(line)) errorLine = lines[line - 1]; + else if (lines?.length > 0) { errorLine = lines[0]; } - const body = errorLine && ( + const body = errorLine ? ( <>
{errorLine}
{column !== null &&
{' '.repeat(column - 1)}^
} + ) : ( + message ); return ( { expect(getByTestId('mock-json-tree')).toBeInTheDocument(); }); +test('renders an object in a tree view in a modal', () => { + const jsonData = { a: 1 }; + const expected = JSON.stringify(jsonData); + const { getByText, getByTestId, queryByTestId } = render( + , + { + useRedux: true, + }, + ); + expect(queryByTestId('mock-json-tree')).not.toBeInTheDocument(); + const link = getByText(expected); + fireEvent.click(link); + expect(getByTestId('mock-json-tree')).toBeInTheDocument(); +}); + test('renders bigInt value in a number format', () => { expect(convertBigIntStrToNumber('123')).toBe('123'); expect(convertBigIntStrToNumber('some string value')).toBe( diff --git a/superset-frontend/src/components/JsonModal/index.tsx b/superset-frontend/src/components/JsonModal/index.tsx index e599f483dcd2..79ff25ef5afc 100644 --- a/superset-frontend/src/components/JsonModal/index.tsx +++ b/superset-frontend/src/components/JsonModal/index.tsx @@ -36,7 +36,7 @@ * under the License. */ import JSONbig from 'json-bigint'; -import { FC } from 'react'; +import { FC, useMemo } from 'react'; import { JSONTree } from 'react-json-tree'; import { useJsonTreeTheme } from 'src/hooks/useJsonTreeTheme'; import Button from '../Button'; @@ -46,6 +46,10 @@ import ModalTrigger from '../ModalTrigger'; export function safeJsonObjectParse( data: unknown, ): null | unknown[] | Record { + if (typeof data === 'object') { + return data as null | unknown[] | Record; + } + // First perform a cheap proxy to avoid calling JSON.parse on data that is clearly not a // JSON object or array if ( @@ -78,7 +82,7 @@ function renderBigIntStrToNumber(value: string | number) { return <>{convertBigIntStrToNumber(value)}; } -type CellDataType = string | number | null; +type CellDataType = string | number | null | object; export interface Props { modalTitle: string; @@ -88,6 +92,11 @@ export interface Props { export const JsonModal: FC = ({ modalTitle, jsonObject, jsonValue }) => { const jsonTreeTheme = useJsonTreeTheme(); + const content = useMemo( + () => + typeof jsonValue === 'object' ? JSON.stringify(jsonValue) : jsonValue, + [jsonValue], + ); return ( = ({ modalTitle, jsonObject, jsonValue }) => { } modalFooter={ } modalTitle={modalTitle} - triggerNode={<>{jsonValue}} + triggerNode={<>{content}} /> ); }; diff --git a/superset-frontend/src/components/MessageToasts/ToastPresenter.tsx b/superset-frontend/src/components/MessageToasts/ToastPresenter.tsx index 59f3d0a6a01d..234995567519 100644 --- a/superset-frontend/src/components/MessageToasts/ToastPresenter.tsx +++ b/superset-frontend/src/components/MessageToasts/ToastPresenter.tsx @@ -31,7 +31,7 @@ const StyledToastPresenter = styled.div` right: 0px; margin-right: 50px; margin-bottom: 50px; - z-index: ${({ theme }) => theme.zIndex.max}; + z-index: ${({ theme }) => theme.zIndex.max + 1}; word-break: break-word; .toast { diff --git a/superset-frontend/src/components/Tooltip/index.tsx b/superset-frontend/src/components/Tooltip/index.tsx index de5eef4b40d2..252aac34025d 100644 --- a/superset-frontend/src/components/Tooltip/index.tsx +++ b/superset-frontend/src/components/Tooltip/index.tsx @@ -18,13 +18,9 @@ */ import { supersetTheme } from '@superset-ui/core'; import { Tooltip as AntdTooltip } from 'antd-v5'; -import { - TooltipProps as AntdTooltipProps, - TooltipPlacement as AntdTooltipPlacement, -} from 'antd-v5/lib/tooltip'; +import { TooltipProps, TooltipPlacement } from 'antd-v5/lib/tooltip'; -export type TooltipPlacement = AntdTooltipPlacement; -export type TooltipProps = AntdTooltipProps; +export { TooltipProps, TooltipPlacement }; export const Tooltip = ({ overlayStyle, ...props }: TooltipProps) => ( <> diff --git a/superset-frontend/src/dashboard/actions/dashboardState.js b/superset-frontend/src/dashboard/actions/dashboardState.js index 33111c75d508..83e8d6356f42 100644 --- a/superset-frontend/src/dashboard/actions/dashboardState.js +++ b/superset-frontend/src/dashboard/actions/dashboardState.js @@ -658,8 +658,61 @@ export function setDirectPathToChild(path) { } export const SET_ACTIVE_TAB = 'SET_ACTIVE_TAB'; + +function findTabsToRestore(tabId, prevTabId, dashboardState, dashboardLayout) { + const { activeTabs: prevActiveTabs, inactiveTabs: prevInactiveTabs } = + dashboardState; + const { present: currentLayout } = dashboardLayout; + const restoredTabs = []; + const queue = [tabId]; + const visited = new Set(); + while (queue.length > 0) { + const seek = queue.shift(); + if (!visited.has(seek)) { + visited.add(seek); + const found = + prevInactiveTabs?.filter(inactiveTabId => + currentLayout[inactiveTabId]?.parents + .filter(id => id.startsWith('TAB-')) + .slice(-1) + .includes(seek), + ) ?? []; + restoredTabs.push(...found); + queue.push(...found); + } + } + const activeTabs = restoredTabs ? [tabId].concat(restoredTabs) : [tabId]; + const tabChanged = Boolean(prevTabId) && tabId !== prevTabId; + const inactiveTabs = tabChanged + ? prevActiveTabs.filter( + activeTabId => + activeTabId !== prevTabId && + currentLayout[activeTabId]?.parents.includes(prevTabId), + ) + : []; + return { + activeTabs, + inactiveTabs, + }; +} + export function setActiveTab(tabId, prevTabId) { - return { type: SET_ACTIVE_TAB, tabId, prevTabId }; + return (dispatch, getState) => { + const { dashboardLayout, dashboardState } = getState(); + const { activeTabs, inactiveTabs } = findTabsToRestore( + tabId, + prevTabId, + dashboardState, + dashboardLayout, + ); + + return dispatch({ + type: SET_ACTIVE_TAB, + activeTabs, + prevTabId, + inactiveTabs, + }); + }; } // Even though SET_ACTIVE_TABS is not being called from Superset's codebase, diff --git a/superset-frontend/src/dashboard/actions/hydrate.js b/superset-frontend/src/dashboard/actions/hydrate.js index 55754b1c271e..693e208fea0a 100644 --- a/superset-frontend/src/dashboard/actions/hydrate.js +++ b/superset-frontend/src/dashboard/actions/hydrate.js @@ -310,7 +310,8 @@ export const hydrateDashboard = isRefreshing: false, isFiltersRefreshing: false, activeTabs: activeTabs || dashboardState?.activeTabs || [], - datasetsStatus: ResourceStatus.Loading, + datasetsStatus: + dashboardState?.datasetsStatus || ResourceStatus.Loading, }, dashboardLayout, }, diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx index 1a0b73165ada..129013565d8e 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx @@ -33,6 +33,7 @@ import { import { storeWithState } from 'spec/fixtures/mockStore'; import mockState from 'spec/fixtures/mockState'; import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants'; +import * as useNativeFiltersModule from './state'; fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {}); @@ -265,4 +266,45 @@ describe('DashboardBuilder', () => { const filterbar = getByTestId('dashboard-filters-panel'); expect(filterbar).toHaveStyleRule('width', `${expectedValue}px`); }); + + it('should not render the filter bar when nativeFiltersEnabled is false', () => { + jest.spyOn(useNativeFiltersModule, 'useNativeFilters').mockReturnValue({ + showDashboard: true, + missingInitialFilters: [], + dashboardFiltersOpen: true, + toggleDashboardFiltersOpen: jest.fn(), + nativeFiltersEnabled: false, + }); + const { queryByTestId } = setup(); + + expect(queryByTestId('dashboard-filters-panel')).not.toBeInTheDocument(); + }); + + it('should render the filter bar when nativeFiltersEnabled is true and not in edit mode', () => { + jest.spyOn(useNativeFiltersModule, 'useNativeFilters').mockReturnValue({ + showDashboard: true, + missingInitialFilters: [], + dashboardFiltersOpen: true, + toggleDashboardFiltersOpen: jest.fn(), + nativeFiltersEnabled: true, + }); + const { queryByTestId } = setup(); + + expect(queryByTestId('dashboard-filters-panel')).toBeInTheDocument(); + }); + + it('should not render the filter bar when in edit mode even if nativeFiltersEnabled is true', () => { + jest.spyOn(useNativeFiltersModule, 'useNativeFilters').mockReturnValue({ + showDashboard: true, + missingInitialFilters: [], + dashboardFiltersOpen: true, + toggleDashboardFiltersOpen: jest.fn(), + nativeFiltersEnabled: true, + }); + const { queryByTestId } = setup({ + dashboardState: { ...mockState.dashboardState, editMode: true }, + }); + + expect(queryByTestId('dashboard-filters-panel')).not.toBeInTheDocument(); + }); }); diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx index e18583ad78b2..66896bb5cc24 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx @@ -473,7 +473,7 @@ const DashboardBuilder = () => { ELEMENT_ON_SCREEN_OPTIONS, ); - const showFilterBar = !editMode; + const showFilterBar = !editMode && nativeFiltersEnabled; const offset = FILTER_BAR_HEADER_HEIGHT + diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts b/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts index ec1cc0bc1f0f..2c45a799f147 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts @@ -29,6 +29,9 @@ import { // eslint-disable-next-line import/prefer-default-export export const useNativeFilters = () => { const [isInitialized, setIsInitialized] = useState(false); + const showNativeFilters = useSelector( + state => getUrlParam(URL_PARAMS.showFilters) ?? true, + ); const canEdit = useSelector( ({ dashboardInfo }) => dashboardInfo.dash_edit_perm, ); @@ -41,7 +44,7 @@ export const useNativeFilters = () => { ); const nativeFiltersEnabled = - canEdit || (!canEdit && filterValues.length !== 0); + showNativeFilters && (canEdit || (!canEdit && filterValues.length !== 0)); const requiredFirstFilter = useMemo( () => filterValues.filter(filter => filter.requiredFirst), diff --git a/superset-frontend/src/dashboard/components/Header/Header.test.tsx b/superset-frontend/src/dashboard/components/Header/Header.test.tsx index 0c679f4415d3..7b28ab44931e 100644 --- a/superset-frontend/src/dashboard/components/Header/Header.test.tsx +++ b/superset-frontend/src/dashboard/components/Header/Header.test.tsx @@ -193,6 +193,10 @@ beforeEach(() => { jest.clearAllMocks(); }); +beforeEach(() => { + window.history.pushState({}, 'Test page', '/dashboard?standalone=1'); +}); + test('should render', () => { const { container } = setup(); expect(container).toBeInTheDocument(); @@ -438,6 +442,36 @@ test('should NOT render MetadataBar when embedded', () => { ).not.toBeInTheDocument(); }); +test('should hide edit button and navbar, and show Exit fullscreen when in fullscreen mode', () => { + const fullscreenState = { + ...initialState, + dashboardState: { + ...initialState.dashboardState, + isFullscreenMode: true, + }, + }; + + setup(fullscreenState); + expect(screen.queryByTestId('edit-dashboard-button')).not.toBeInTheDocument(); + expect(screen.getByTestId('actions-trigger')).toBeInTheDocument(); + expect(screen.queryByTestId('main-navigation')).not.toBeInTheDocument(); +}); + +test('should show Exit fullscreen when in fullscreen mode', async () => { + setup(); + + fireEvent.click(screen.getByTestId('actions-trigger')); + + expect(await screen.findByText('Exit fullscreen')).toBeInTheDocument(); +}); + +test('should have fullscreen option in dropdown', async () => { + setup(); + await openActionsDropdown(); + expect(screen.getByText('Exit fullscreen')).toBeInTheDocument(); + expect(screen.queryByText('Enter fullscreen')).not.toBeInTheDocument(); +}); + test('should render MetadataBar when not in edit mode and not embedded', () => { const state = { dashboardInfo: { diff --git a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.tsx b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.tsx index 6bc712f6b849..1b41703c42b6 100644 --- a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.tsx +++ b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.tsx @@ -97,11 +97,13 @@ export class HeaderActionsDropdown extends PureComponent< this.props.showPropertiesModal(); break; case MenuKeys.ToggleFullscreen: { + const isCurrentlyStandalone = + Number(getUrlParam(URL_PARAMS.standalone)) === 1; const url = getDashboardUrl({ pathname: window.location.pathname, filters: getActiveFilters(), hash: window.location.hash, - standalone: getUrlParam(URL_PARAMS.standalone), + standalone: isCurrentlyStandalone ? null : 1, }); window.location.replace(url); break; diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx index 5cc8ba6d37f6..299f329b675b 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx @@ -150,6 +150,9 @@ const Chart = props => { const emitCrossFilters = useSelector( state => !!state.dashboardInfo.crossFiltersEnabled, ); + const maxRows = useSelector( + state => state.dashboardInfo.common.conf.SQL_MAX_ROW, + ); const datasource = useSelector( state => (chart && @@ -157,6 +160,7 @@ const Chart = props => { state.datasources[chart.form_data.datasource]) || PLACEHOLDER_DATASOURCE, ); + const dashboardInfo = useSelector(state => state.dashboardInfo); const [descriptionHeight, setDescriptionHeight] = useState(0); const [height, setHeight] = useState(props.height); @@ -304,6 +308,8 @@ const Chart = props => { ], ); + formData.dashboardId = dashboardInfo.id; + const onExploreChart = useCallback( async clickEvent => { const isOpenInNewTab = @@ -357,9 +363,7 @@ const Chart = props => { is_cached: props.isCached, }); exportChart({ - formData: isFullCSV - ? { ...formData, row_limit: props.maxRows } - : formData, + formData: isFullCSV ? { ...formData, row_limit: maxRows } : formData, resultType: isPivot ? 'post_processed' : 'full', resultFormat: format, force: true, @@ -376,16 +380,13 @@ const Chart = props => { ], ); - const exportCSV = useCallback( - (isFullCSV = false) => { - exportTable('csv', isFullCSV); - }, - [exportTable], - ); + const exportCSV = useCallback(() => { + exportTable('csv', false); + }, [exportTable]); const exportFullCSV = useCallback(() => { - exportCSV(true); - }, [exportCSV]); + exportTable('csv', true); + }, [exportTable]); const exportPivotCSV = useCallback(() => { exportTable('csv', false, true); diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.test.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.test.jsx index 35be0144af76..950f3dacc120 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Chart.test.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.test.jsx @@ -34,7 +34,7 @@ const props = { height: 100, updateSliceName() {}, // from redux - maxRows: 666, + maxRows: 500, // will be overwritten with SQL_MAX_ROW from conf formData: chartQueries[queryId].form_data, datasource: mockDatasource[sliceEntities.slices[queryId].datasource], sliceName: sliceEntities.slices[queryId].slice_name, @@ -74,10 +74,11 @@ const defaultState = { datasources: mockDatasource, dashboardState: { editMode: false, expandedSlices: {} }, dashboardInfo: { + id: props.dashboardId, superset_can_explore: false, superset_can_share: false, superset_can_csv: false, - common: { conf: { SUPERSET_WEBSERVER_TIMEOUT: 0 } }, + common: { conf: { SUPERSET_WEBSERVER_TIMEOUT: 0, SQL_MAX_ROW: 666 } }, }, }; @@ -165,7 +166,9 @@ test('should call exportChart when exportCSV is clicked', async () => { expect(stubbedExportCSV).toHaveBeenCalledTimes(1); expect(stubbedExportCSV).toHaveBeenCalledWith( expect.objectContaining({ - formData: expect.anything(), + formData: expect.objectContaining({ + dashboardId: 111, + }), resultType: 'full', resultFormat: 'csv', }), @@ -195,6 +198,7 @@ test('should call exportChart with row_limit props.maxRows when exportFullCSV is expect.objectContaining({ formData: expect.objectContaining({ row_limit: 666, + dashboardId: 111, }), resultType: 'full', resultFormat: 'csv', @@ -249,6 +253,7 @@ test('should call exportChart with row_limit props.maxRows when exportFullXLSX i expect.objectContaining({ formData: expect.objectContaining({ row_limit: 666, + dashboardId: 111, }), resultType: 'full', resultFormat: 'xlsx', diff --git a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.tsx b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.tsx index 887f1baa73f5..b78b217aad97 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.tsx +++ b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.tsx @@ -96,7 +96,7 @@ const ChartHolder = ({ const theme = useTheme(); const fullSizeStyle = css` && { - position: fixed; + position: fixed !important; z-index: 3000; left: 0; top: 0; diff --git a/superset-frontend/src/dashboard/components/gridComponents/Row.jsx b/superset-frontend/src/dashboard/components/gridComponents/Row.jsx index bd59306ca97f..23cf2d45a4d3 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Row.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Row.jsx @@ -51,6 +51,7 @@ import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu'; import { componentShape } from 'src/dashboard/util/propShapes'; import backgroundStyleOptions from 'src/dashboard/util/backgroundStyleOptions'; import { BACKGROUND_TRANSPARENT } from 'src/dashboard/util/constants'; +import { isEmbedded } from 'src/dashboard/util/isEmbedded'; import { EMPTY_CONTAINER_Z_INDEX } from 'src/dashboard/constants'; import { isCurrentUserBot } from 'src/utils/isBot'; import { useDebouncedEffect } from '../../../explore/exploreUtils'; @@ -188,7 +189,10 @@ const Row = props => { observerDisabler = new IntersectionObserver( ([entry]) => { if (!entry.isIntersecting && isComponentVisibleRef.current) { - setIsInView(false); + // Reference: https://www.w3.org/TR/intersection-observer/#dom-intersectionobserver-rootmargin + if (!isEmbedded()) { + setIsInView(false); + } } }, { diff --git a/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.tsx b/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.tsx index 0845e2e964b9..7081445c9d06 100644 --- a/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.tsx +++ b/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.tsx @@ -115,10 +115,12 @@ export default class WithPopoverMenu extends PureComponent< onChangeFocus: null, menuItems: [], isFocused: false, - shouldFocus: (event: any, container: ShouldFocusContainer) => - container?.contains(event.target) || - event.target.id === 'menu-item' || - event.target.parentNode?.id === 'menu-item', + shouldFocus: (event: any, container: ShouldFocusContainer) => { + if (container?.contains(event.target)) return true; + if (event.target.id === 'menu-item') return true; + if (event.target.parentNode?.id === 'menu-item') return true; + return false; + }, style: null, }; @@ -156,6 +158,9 @@ export default class WithPopoverMenu extends PureComponent< if (!this.props.editMode) { return; } + + event.stopPropagation(); + const { onChangeFocus, shouldFocus: shouldFocusFunc, diff --git a/superset-frontend/src/dashboard/components/nativeFilters/state.test.ts b/superset-frontend/src/dashboard/components/nativeFilters/state.test.ts new file mode 100644 index 000000000000..7cd694b4ca9f --- /dev/null +++ b/superset-frontend/src/dashboard/components/nativeFilters/state.test.ts @@ -0,0 +1,126 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { useSelector } from 'react-redux'; +import { NativeFilterType, Filter, Divider } from '@superset-ui/core'; +import { useIsFilterInScope, useSelectFiltersInScope } from './state'; + +jest.mock('react-redux', () => { + const actual = jest.requireActual('react-redux'); + return { + ...actual, + useSelector: jest.fn(), + }; +}); + +beforeEach(() => { + (useSelector as jest.Mock).mockImplementation(selector => { + if (selector.name === 'useActiveDashboardTabs') { + return ['TAB_1']; + } + return []; + }); +}); + +describe('useIsFilterInScope', () => { + it('should return true for dividers (always in scope)', () => { + const divider: Divider = { + id: 'divider_1', + type: NativeFilterType.Divider, + title: 'Sample Divider', + description: 'Divider description', + }; + + const { result } = renderHook(() => useIsFilterInScope()); + expect(result.current(divider)).toBe(true); + }); + + it('should return true for filters with charts in active tabs', () => { + const filter: Filter = { + id: 'filter_1', + name: 'Test Filter', + filterType: 'filter_select', + type: NativeFilterType.NativeFilter, + chartsInScope: [123], + scope: { rootPath: ['TAB_1'], excluded: [] }, + controlValues: {}, + defaultDataMask: {}, + cascadeParentIds: [], + targets: [{ column: { name: 'column_name' }, datasetId: 1 }], + description: 'Sample filter description', + }; + + const { result } = renderHook(() => useIsFilterInScope()); + expect(result.current(filter)).toBe(true); + }); + + it('should return false for filters with inactive rootPath', () => { + const filter: Filter = { + id: 'filter_3', + name: 'Test Filter 3', + filterType: 'filter_select', + type: NativeFilterType.NativeFilter, + scope: { rootPath: ['TAB_99'], excluded: [] }, + controlValues: {}, + defaultDataMask: {}, + cascadeParentIds: [], + targets: [{ column: { name: 'column_name' }, datasetId: 3 }], + description: 'Sample filter description', + }; + + const { result } = renderHook(() => useIsFilterInScope()); + expect(result.current(filter)).toBe(false); + }); +}); + +describe('useSelectFiltersInScope', () => { + it('should return all filters in scope when no tabs exist', () => { + const filters: Filter[] = [ + { + id: 'filter_1', + name: 'Filter 1', + filterType: 'filter_select', + type: NativeFilterType.NativeFilter, + scope: { rootPath: [], excluded: [] }, + controlValues: {}, + defaultDataMask: {}, + cascadeParentIds: [], + targets: [{ column: { name: 'column_name' }, datasetId: 1 }], + description: 'Sample filter description', + }, + { + id: 'filter_2', + name: 'Filter 2', + filterType: 'filter_select', + type: NativeFilterType.NativeFilter, + scope: { rootPath: [], excluded: [] }, + controlValues: {}, + defaultDataMask: {}, + cascadeParentIds: [], + targets: [{ column: { name: 'column_name' }, datasetId: 2 }], + description: 'Sample filter description', + }, + ]; + + const { result } = renderHook(() => useSelectFiltersInScope(filters)); + expect(result.current[0]).toEqual(filters); + expect(result.current[1]).toEqual([]); + }); +}); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/state.ts b/superset-frontend/src/dashboard/components/nativeFilters/state.ts index 498fcf9a77c5..3404107498f5 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/state.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/state.ts @@ -106,16 +106,27 @@ export function useIsFilterInScope() { // Chart is in an active tab tree if all of its ancestors of type TAB are active // Dividers are always in scope return useCallback( - (filter: Filter | Divider) => - isFilterDivider(filter) || - ('chartsInScope' in filter && - filter.chartsInScope?.some((chartId: number) => { + (filter: Filter | Divider) => { + if (isFilterDivider(filter)) return true; + + const isChartInScope = + Array.isArray(filter.chartsInScope) && + filter.chartsInScope.length > 0 && + filter.chartsInScope.some((chartId: number) => { const tabParents = selectChartTabParents(chartId); return ( - tabParents?.length === 0 || - tabParents?.every(tab => activeTabs.includes(tab)) + !tabParents || + tabParents.length === 0 || + tabParents.every(tab => activeTabs.includes(tab)) ); - })), + }); + + const isFilterInActiveTab = + filter.scope?.rootPath && + filter.scope.rootPath.some(tab => activeTabs.includes(tab)); + + return isChartInScope || isFilterInActiveTab; + }, [selectChartTabParents, activeTabs], ); } diff --git a/superset-frontend/src/dashboard/reducers/dashboardState.js b/superset-frontend/src/dashboard/reducers/dashboardState.js index 771a56301659..3c7c65c60114 100644 --- a/superset-frontend/src/dashboard/reducers/dashboardState.js +++ b/superset-frontend/src/dashboard/reducers/dashboardState.js @@ -209,12 +209,17 @@ export default function dashboardStateReducer(state = {}, action) { }; }, [SET_ACTIVE_TAB]() { - const newActiveTabs = new Set(state.activeTabs); - newActiveTabs.delete(action.prevTabId); - newActiveTabs.add(action.tabId); + const newActiveTabs = new Set(state.activeTabs).difference( + new Set(action.inactiveTabs.concat(action.prevTabId)), + ); + const newInactiveTabs = new Set(state.inactiveTabs) + .difference(new Set(action.activeTabs)) + .union(new Set(action.inactiveTabs)); + return { ...state, - activeTabs: Array.from(newActiveTabs), + inactiveTabs: Array.from(newInactiveTabs), + activeTabs: Array.from(newActiveTabs.union(new Set(action.activeTabs))), }; }, [SET_ACTIVE_TABS]() { diff --git a/superset-frontend/src/dashboard/reducers/dashboardState.test.ts b/superset-frontend/src/dashboard/reducers/dashboardState.test.ts index fea67149fac6..5e77b4102223 100644 --- a/superset-frontend/src/dashboard/reducers/dashboardState.test.ts +++ b/superset-frontend/src/dashboard/reducers/dashboardState.test.ts @@ -16,24 +16,112 @@ * specific language governing permissions and limitations * under the License. */ - +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; import dashboardStateReducer from './dashboardState'; import { setActiveTab, setActiveTabs } from '../actions/dashboardState'; +const middlewares = [thunk]; +const mockStore = configureMockStore(middlewares); + describe('DashboardState reducer', () => { - it('SET_ACTIVE_TAB', () => { - expect( - dashboardStateReducer({ activeTabs: [] }, setActiveTab('tab1')), - ).toEqual({ activeTabs: ['tab1'] }); - expect( - dashboardStateReducer({ activeTabs: ['tab1'] }, setActiveTab('tab1')), - ).toEqual({ activeTabs: ['tab1'] }); - expect( - dashboardStateReducer( - { activeTabs: ['tab1'] }, - setActiveTab('tab2', 'tab1'), - ), - ).toEqual({ activeTabs: ['tab2'] }); + describe('SET_ACTIVE_TAB', () => { + it('switches a single tab', () => { + const store = mockStore({ + dashboardState: { activeTabs: [] }, + dashboardLayout: { present: { tab1: { parents: [] } } }, + }); + const request = setActiveTab('tab1'); + const thunkAction = request(store.dispatch, store.getState); + + expect(dashboardStateReducer({ activeTabs: [] }, thunkAction)).toEqual({ + activeTabs: ['tab1'], + inactiveTabs: [], + }); + + const request2 = setActiveTab('tab2', 'tab1'); + const thunkAction2 = request2(store.dispatch, store.getState); + expect( + dashboardStateReducer({ activeTabs: ['tab1'] }, thunkAction2), + ).toEqual({ activeTabs: ['tab2'], inactiveTabs: [] }); + }); + + it('switches a multi-depth tab', () => { + const initState = { activeTabs: ['TAB-1', 'TAB-A', 'TAB-__a'] }; + const store = mockStore({ + dashboardState: initState, + dashboardLayout: { + present: { + 'TAB-1': { parents: [] }, + 'TAB-2': { parents: [] }, + 'TAB-A': { parents: ['TAB-1', 'TABS-1'] }, + 'TAB-B': { parents: ['TAB-1', 'TABS-1'] }, + 'TAB-__a': { parents: ['TAB-1', 'TABS-1', 'TAB-A', 'TABS-A'] }, + 'TAB-__b': { parents: ['TAB-1', 'TABS-1', 'TAB-B', 'TABS-B'] }, + }, + }, + }); + let request = setActiveTab('TAB-B', 'TAB-A'); + let thunkAction = request(store.dispatch, store.getState); + let result = dashboardStateReducer( + { activeTabs: ['TAB-1', 'TAB-A', 'TAB-__a'] }, + thunkAction, + ); + expect(result).toEqual({ + activeTabs: expect.arrayContaining(['TAB-1', 'TAB-B']), + inactiveTabs: ['TAB-__a'], + }); + request = setActiveTab('TAB-2', 'TAB-1'); + thunkAction = request(store.dispatch, () => ({ + ...(store.getState() ?? {}), + dashboardState: result, + })); + result = dashboardStateReducer(result, thunkAction); + expect(result).toEqual({ + activeTabs: ['TAB-2'], + inactiveTabs: expect.arrayContaining(['TAB-B', 'TAB-__a']), + }); + request = setActiveTab('TAB-1', 'TAB-2'); + thunkAction = request(store.dispatch, () => ({ + ...(store.getState() ?? {}), + dashboardState: result, + })); + result = dashboardStateReducer(result, thunkAction); + expect(result).toEqual({ + activeTabs: expect.arrayContaining(['TAB-1', 'TAB-B']), + inactiveTabs: ['TAB-__a'], + }); + request = setActiveTab('TAB-A', 'TAB-B'); + thunkAction = request(store.dispatch, () => ({ + ...(store.getState() ?? {}), + dashboardState: result, + })); + result = dashboardStateReducer(result, thunkAction); + expect(result).toEqual({ + activeTabs: expect.arrayContaining(['TAB-1', 'TAB-A', 'TAB-__a']), + inactiveTabs: [], + }); + request = setActiveTab('TAB-2', 'TAB-1'); + thunkAction = request(store.dispatch, () => ({ + ...(store.getState() ?? {}), + dashboardState: result, + })); + result = dashboardStateReducer(result, thunkAction); + expect(result).toEqual({ + activeTabs: expect.arrayContaining(['TAB-2']), + inactiveTabs: ['TAB-A', 'TAB-__a'], + }); + request = setActiveTab('TAB-1', 'TAB-2'); + thunkAction = request(store.dispatch, () => ({ + ...(store.getState() ?? {}), + dashboardState: result, + })); + result = dashboardStateReducer(result, thunkAction); + expect(result).toEqual({ + activeTabs: expect.arrayContaining(['TAB-1', 'TAB-A', 'TAB-__a']), + inactiveTabs: [], + }); + }); }); it('SET_ACTIVE_TABS', () => { expect( diff --git a/superset-frontend/src/dashboard/util/isEmbedded.ts b/superset-frontend/src/dashboard/util/isEmbedded.ts new file mode 100644 index 000000000000..3c8d30fcce33 --- /dev/null +++ b/superset-frontend/src/dashboard/util/isEmbedded.ts @@ -0,0 +1,26 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const isEmbedded = () => { + try { + return window.self !== window.top || window.frameElement !== null; + } catch (e) { + return true; + } +}; diff --git a/superset-frontend/src/explore/actions/exploreActions.ts b/superset-frontend/src/explore/actions/exploreActions.ts index da702ac16ff5..25b03ec9f2d1 100644 --- a/superset-frontend/src/explore/actions/exploreActions.ts +++ b/superset-frontend/src/explore/actions/exploreActions.ts @@ -164,6 +164,21 @@ export function setStashFormData( }; } +export const START_METADATA_LOADING = 'START_METADATA_LOADING'; +export function startMetaDataLoading() { + return { type: START_METADATA_LOADING }; +} + +export const STOP_METADATA_LOADING = 'STOP_METADATA_LOADING'; +export function stopMetaDataLoading() { + return { type: STOP_METADATA_LOADING }; +} + +export const SYNC_DATASOURCE_METADATA = 'SYNC_DATASOURCE_METADATA'; +export function syncDatasourceMetadata(datasource: Dataset) { + return { type: SYNC_DATASOURCE_METADATA, datasource }; +} + export const exploreActions = { ...toastActions, fetchDatasourcesStarted, @@ -178,6 +193,7 @@ export const exploreActions = { createNewSlice, sliceUpdated, setForceQuery, + syncDatasourceMetadata, }; export type ExploreActions = typeof exploreActions; diff --git a/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx b/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx index 229d21aa2c04..4f06231bc659 100644 --- a/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx +++ b/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx @@ -359,7 +359,14 @@ function ExploreViewContainer(props) { } useComponentDidMount(() => { - props.actions.logEvent(LOG_ACTIONS_MOUNT_EXPLORER); + props.actions.logEvent( + LOG_ACTIONS_MOUNT_EXPLORER, + props.slice?.slice_id + ? { + slice_id: props.slice.slice_id, + } + : undefined, + ); }); useChangeEffect(tabId, (previous, current) => { diff --git a/superset-frontend/src/explore/components/controls/ColorPickerControl.jsx b/superset-frontend/src/explore/components/controls/ColorPickerControl.jsx index 1d140db0ae76..56b86e58947f 100644 --- a/superset-frontend/src/explore/components/controls/ColorPickerControl.jsx +++ b/superset-frontend/src/explore/components/controls/ColorPickerControl.jsx @@ -78,7 +78,7 @@ export default class ColorPickerControl extends Component { renderPopover() { const presetColors = getCategoricalSchemeRegistry() .get() - .colors.filter((s, i) => i < 7); + .colors.filter((s, i) => i < 9); return (
{ }); expect(adhocFilter2.translateToSql()).toBe('SUM(value) <> 5'); }); - it('sets comparator to null when operator is IS_NULL', () => { - const adhocFilter2 = new AdhocFilter({ + it('sets comparator to undefined when operator is IS_NULL', () => { + const adhocFilter = new AdhocFilter({ expressionType: ExpressionTypes.Simple, subject: 'SUM(value)', operator: 'IS NULL', @@ -234,10 +234,10 @@ describe('AdhocFilter', () => { comparator: '5', clause: Clauses.Having, }); - expect(adhocFilter2.comparator).toBe(null); + expect(adhocFilter.comparator).toBe(undefined); }); - it('sets comparator to null when operator is IS_NOT_NULL', () => { - const adhocFilter2 = new AdhocFilter({ + it('sets comparator to undefined when operator is IS_NOT_NULL', () => { + const adhocFilter = new AdhocFilter({ expressionType: ExpressionTypes.Simple, subject: 'SUM(value)', operator: 'IS NOT NULL', @@ -245,38 +245,60 @@ describe('AdhocFilter', () => { comparator: '5', clause: Clauses.Having, }); - expect(adhocFilter2.comparator).toBe(null); + expect(adhocFilter.comparator).toBe(undefined); + }); + it('sets comparator to undefined when operator is IS_TRUE', () => { + const adhocFilter = new AdhocFilter({ + expressionType: ExpressionTypes.Simple, + subject: 'col', + operator: 'IS TRUE', + operatorId: Operators.IsTrue, + comparator: '5', + clause: Clauses.Having, + }); + expect(adhocFilter.comparator).toBe(undefined); + }); + it('sets comparator to undefined when operator is IS_FALSE', () => { + const adhocFilter = new AdhocFilter({ + expressionType: ExpressionTypes.Simple, + subject: 'col', + operator: 'IS FALSE', + operatorId: Operators.IsFalse, + comparator: '5', + clause: Clauses.Having, + }); + expect(adhocFilter.comparator).toBe(undefined); }); it('sets the label properly if subject is a string', () => { - const adhocFilter2 = new AdhocFilter({ + const adhocFilter = new AdhocFilter({ expressionType: ExpressionTypes.Simple, subject: 'order_date', }); - expect(adhocFilter2.getDefaultLabel()).toBe('order_date'); + expect(adhocFilter.getDefaultLabel()).toBe('order_date'); }); it('sets the label properly if subject is an object with the column_date property', () => { - const adhocFilter2 = new AdhocFilter({ + const adhocFilter = new AdhocFilter({ expressionType: ExpressionTypes.Simple, subject: { column_name: 'year', }, }); - expect(adhocFilter2.getDefaultLabel()).toBe('year'); + expect(adhocFilter.getDefaultLabel()).toBe('year'); }); it('sets the label to empty is there is no column_name in the object', () => { - const adhocFilter2 = new AdhocFilter({ + const adhocFilter = new AdhocFilter({ expressionType: ExpressionTypes.Simple, subject: { unknown: 'year', }, }); - expect(adhocFilter2.getDefaultLabel()).toBe(''); + expect(adhocFilter.getDefaultLabel()).toBe(''); }); it('sets the label to empty is there is no subject', () => { - const adhocFilter2 = new AdhocFilter({ + const adhocFilter = new AdhocFilter({ expressionType: ExpressionTypes.Simple, subject: undefined, }); - expect(adhocFilter2.getDefaultLabel()).toBe(''); + expect(adhocFilter.getDefaultLabel()).toBe(''); }); }); diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js index 92cb69eebcab..7c1323657373 100644 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js @@ -18,7 +18,7 @@ */ import { CUSTOM_OPERATORS, - Operators, + DISABLE_INPUT_OPERATORS, OPERATOR_ENUM_TO_OPERATOR_TYPE, } from 'src/explore/constants'; import { translateToSql } from '../utils/translateToSQL'; @@ -36,18 +36,8 @@ export default class AdhocFilter { this.operator = adhocFilter.operator?.toUpperCase(); this.operatorId = adhocFilter.operatorId; this.comparator = adhocFilter.comparator; - if ( - [Operators.IsTrue, Operators.IsFalse].indexOf(adhocFilter.operatorId) >= - 0 - ) { - this.comparator = adhocFilter.operatorId === Operators.IsTrue; - } - if ( - [Operators.IsNull, Operators.IsNotNull].indexOf( - adhocFilter.operatorId, - ) >= 0 - ) { - this.comparator = null; + if (DISABLE_INPUT_OPERATORS.indexOf(adhocFilter.operatorId) >= 0) { + this.comparator = undefined; } this.clause = adhocFilter.clause || Clauses.Where; this.sqlExpression = null; @@ -103,34 +93,30 @@ export default class AdhocFilter { } isValid() { - const nullCheckOperators = [Operators.IsNotNull, Operators.IsNull].map( - op => OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation, - ); - const truthCheckOperators = [Operators.IsTrue, Operators.IsFalse].map( - op => OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation, - ); if (this.expressionType === ExpressionTypes.Simple) { - if (nullCheckOperators.indexOf(this.operator) >= 0) { - return !!(this.operator && this.subject); - } - if (truthCheckOperators.indexOf(this.operator) >= 0) { - return !!(this.subject && this.comparator !== null); + // operators where the comparator is not used + if ( + DISABLE_INPUT_OPERATORS.map( + op => OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation, + ).indexOf(this.operator) >= 0 + ) { + return !!this.subject; } + if (this.operator && this.subject && this.clause) { if (Array.isArray(this.comparator)) { - if (this.comparator.length > 0) { - // A non-empty array of values ('IN' or 'NOT IN' clauses) - return true; - } - } else if (this.comparator !== null) { - // A value has been selected or typed - return true; + // A non-empty array of values ('IN' or 'NOT IN' clauses) + return this.comparator.length > 0; } + // A value has been selected or typed + return this.comparator !== null; } - } else if (this.expressionType === ExpressionTypes.Sql) { - return !!(this.sqlExpression && this.clause); } - return false; + + return ( + this.expressionType === ExpressionTypes.Sql && + !!(this.sqlExpression && this.clause) + ); } getDefaultLabel() { diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx index 895d2ab50b5c..27e1f9ab0b14 100644 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx @@ -331,25 +331,25 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => { expect(isOperatorRelevant(operator, 'value')).toBe(true); }); }); - it('sets comparator to true when operator is IS_TRUE', () => { + it('sets comparator to undefined when operator is IS_TRUE', () => { const props = setup(); const { onOperatorChange } = useSimpleTabFilterProps(props); onOperatorChange(Operators.IsTrue); expect(props.onChange.calledOnce).toBe(true); expect(props.onChange.lastCall.args[0].operatorId).toBe(Operators.IsTrue); - expect(props.onChange.lastCall.args[0].operator).toBe('=='); - expect(props.onChange.lastCall.args[0].comparator).toBe(true); + expect(props.onChange.lastCall.args[0].operator).toBe('IS TRUE'); + expect(props.onChange.lastCall.args[0].comparator).toBe(undefined); }); - it('sets comparator to false when operator is IS_FALSE', () => { + it('sets comparator to undefined when operator is IS_FALSE', () => { const props = setup(); const { onOperatorChange } = useSimpleTabFilterProps(props); onOperatorChange(Operators.IsFalse); expect(props.onChange.calledOnce).toBe(true); expect(props.onChange.lastCall.args[0].operatorId).toBe(Operators.IsFalse); - expect(props.onChange.lastCall.args[0].operator).toBe('=='); - expect(props.onChange.lastCall.args[0].comparator).toBe(false); + expect(props.onChange.lastCall.args[0].operator).toBe('IS FALSE'); + expect(props.onChange.lastCall.args[0].comparator).toBe(undefined); }); - it('sets comparator to null when operator is IS_NULL or IS_NOT_NULL', () => { + it('sets comparator to undefined when operator is IS_NULL or IS_NOT_NULL', () => { const props = setup(); const { onOperatorChange } = useSimpleTabFilterProps(props); [Operators.IsNull, Operators.IsNotNull].forEach(op => { @@ -359,7 +359,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => { expect(props.onChange.lastCall.args[0].operator).toBe( OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation, ); - expect(props.onChange.lastCall.args[0].comparator).toBe(null); + expect(props.onChange.lastCall.args[0].comparator).toBe(undefined); }); }); }); diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx index 904c005c14e6..bfd2b409a8e3 100644 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx @@ -209,9 +209,6 @@ export const useSimpleTabFilterProps = (props: Props) => { ? currentComparator[0] : currentComparator; } - if (operatorId === Operators.IsTrue || operatorId === Operators.IsFalse) { - newComparator = Operators.IsTrue === operatorId; - } if (operatorId && CUSTOM_OPERATORS.has(operatorId)) { props.onChange( props.adhocFilter.duplicateWith({ diff --git a/superset-frontend/src/explore/components/controls/TextAreaControl.jsx b/superset-frontend/src/explore/components/controls/TextAreaControl.jsx index fc0545d8c44e..e8f165c8e118 100644 --- a/superset-frontend/src/explore/components/controls/TextAreaControl.jsx +++ b/superset-frontend/src/explore/components/controls/TextAreaControl.jsx @@ -19,6 +19,10 @@ import { Component } from 'react'; import PropTypes from 'prop-types'; import { TextArea } from 'src/components/Input'; +import { + Tooltip, + TooltipProps as TooltipOptions, +} from 'src/components/Tooltip'; import { t, withTheme } from '@superset-ui/core'; import Button from 'src/components/Button'; @@ -55,6 +59,7 @@ const propTypes = { 'vertical', ]), textAreaStyles: PropTypes.object, + tooltipOptions: PropTypes.oneOf([null, TooltipOptions]), }; const defaultProps = { @@ -67,6 +72,7 @@ const defaultProps = { readOnly: false, resize: null, textAreaStyles: {}, + tooltipOptions: {}, }; class TextAreaControl extends Component { @@ -94,31 +100,44 @@ class TextAreaControl extends Component { if (this.props.readOnly) { style.backgroundColor = '#f2f2f2'; } + const codeEditor = ( +
+ +
+ ); + + if (this.props.tooltipOptions) { + return {codeEditor}; + } + return codeEditor; + } - return ( - +