Compare commits

..

1 Commits

Author SHA1 Message Date
Beto Dealmeida
7083d09777 feat: file handler for CSV/XSL 2025-12-19 09:47:32 -05:00
239 changed files with 6342 additions and 14081 deletions

View File

@@ -1,41 +0,0 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Auto-configure Docker Compose for multi-instance support
# Requires direnv: https://direnv.net/
#
# Install: brew install direnv (or apt install direnv)
# Setup: Add 'eval "$(direnv hook bash)"' to ~/.bashrc (or ~/.zshrc)
# Allow: Run 'direnv allow' in this directory once
# Generate unique project name from directory
export COMPOSE_PROJECT_NAME=$(basename "$PWD" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g')
# Find available ports sequentially to avoid collisions
_is_free() { ! lsof -i ":$1" &>/dev/null 2>&1; }
_p=80; while ! _is_free $_p; do ((_p++)); done; export NGINX_PORT=$_p
_p=8088; while ! _is_free $_p; do ((_p++)); done; export SUPERSET_PORT=$_p
_p=9000; while ! _is_free $_p; do ((_p++)); done; export NODE_PORT=$_p
_p=8080; while ! _is_free $_p || [ $_p -eq $NGINX_PORT ]; do ((_p++)); done; export WEBSOCKET_PORT=$_p
_p=8081; while ! _is_free $_p || [ $_p -eq $WEBSOCKET_PORT ]; do ((_p++)); done; export CYPRESS_PORT=$_p
_p=5432; while ! _is_free $_p; do ((_p++)); done; export DATABASE_PORT=$_p
_p=6379; while ! _is_free $_p; do ((_p++)); done; export REDIS_PORT=$_p
unset _p _is_free
echo "🐳 Superset configured: http://localhost:$SUPERSET_PORT (dev: localhost:$NODE_PORT)"

View File

@@ -41,8 +41,8 @@ body:
label: Superset version
options:
- master / latest-dev
- "6.0.0"
- "5.0.0"
- "4.1.3"
validations:
required: true
- type: dropdown

View File

@@ -54,7 +54,7 @@ repos:
exclude: ^helm/superset/templates/
- id: debug-statements
- id: end-of-file-fixer
exclude: .*/lerna\.json$|^docs/static/img/logos/
exclude: .*/lerna\.json$
- id: trailing-whitespace
exclude: ^.*\.(snap)
args: ["--markdown-linebreak-ext=md"]

View File

@@ -76,9 +76,6 @@ snowflake.svg
ydb.svg
loading.svg
# docs third-party logos, i.e. docs/static/img/logos/*
logos/*
# docs-related
erd.puml
erd.svg
@@ -86,7 +83,6 @@ intro_header.txt
# for LLMs
llm-context.md
llms.txt
AGENTS.md
LLMS.md
CLAUDE.md

View File

@@ -49,4 +49,3 @@ under the License.
- [4.1.3](./CHANGELOG/4.1.3.md)
- [4.1.4](./CHANGELOG/4.1.4.md)
- [5.0.0](./CHANGELOG/5.0.0.md)
- [6.0.0](./CHANGELOG/6.0.0.md)

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@
# Python version installed; we need 3.10-3.11
PYTHON=`command -v python3.11 || command -v python3.10`
.PHONY: install superset venv pre-commit up down logs ps nuke
.PHONY: install superset venv pre-commit
install: superset pre-commit
@@ -112,22 +112,3 @@ report-celery-beat:
admin-user:
superset fab create-admin
# Docker Compose with auto-assigned ports (for running multiple instances)
up:
./scripts/docker-compose-up.sh
up-detached:
./scripts/docker-compose-up.sh -d
down:
./scripts/docker-compose-up.sh down
logs:
./scripts/docker-compose-up.sh logs -f
ps:
./scripts/docker-compose-up.sh ps
nuke:
./scripts/docker-compose-up.sh nuke

View File

@@ -55,7 +55,7 @@ A modern, enterprise-ready business intelligence web application.
[**Get Involved**](#get-involved) |
[**Contributor Guide**](#contributor-guide) |
[**Resources**](#resources) |
[**Organizations Using Superset**](https://superset.apache.org/inTheWild)
[**Organizations Using Superset**](https://github.com/apache/superset/blob/master/RESOURCES/INTHEWILD.md)
## Why Superset?
@@ -171,7 +171,7 @@ how to set up a development environment.
## Resources
- [Superset "In the Wild"](https://superset.apache.org/inTheWild) - see who's using Superset, and [add your organization](https://github.com/apache/superset/edit/master/RESOURCES/INTHEWILD.yaml) to the list!
- [Superset "In the Wild"](https://github.com/apache/superset/blob/master/RESOURCES/INTHEWILD.md) - open a PR to add your org to the list!
- [Feature Flags](https://github.com/apache/superset/blob/master/RESOURCES/FEATURE_FLAGS.md) - the status of Superset's Feature Flags.
- [Standard Roles](https://github.com/apache/superset/blob/master/RESOURCES/STANDARD_ROLES.md) - How RBAC permissions map to roles.
- [Superset Wiki](https://github.com/apache/superset/wiki) - Tons of additional community resources: best practices, community content and other information.

226
RESOURCES/INTHEWILD.md Normal file
View File

@@ -0,0 +1,226 @@
<!--
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.
-->
## Superset Users in the Wild
Here's a list of organizations, broken down into broad industry categories, that have taken the time to send a PR to let
the world know they are using Apache Superset. If you are a user and want to be recognized,
all you have to do is file a simple PR [like this one](https://github.com/apache/superset/pull/10122) — [just click here](https://github.com/apache/superset/edit/master/RESOURCES/INTHEWILD.md) to do so. If you think
the categorization is inaccurate, please file a PR with your correction as well.
Join our growing community!
### Sharing Economy
- [Airbnb](https://github.com/airbnb)
- [Faasos](https://faasos.com/) [@shashanksingh]
- [Free2Move](https://www.free2move.com/) [@PaoloTerzi]
- [Hostnfly](https://www.hostnfly.com/) [@alexisrosuel]
- [Lime](https://www.li.me/) [@cxmcc]
- [Lyft](https://www.lyft.com/)
- [Ontruck](https://www.ontruck.com/)
### Financial Services
- [Aktia Bank plc](https://www.aktia.com)
- [American Express](https://www.americanexpress.com) [@TheLastSultan]
- [bumper](https://www.bumper.co/) [@vasu-ram, @JamiePercival]
- [Cape Crypto](https://capecrypto.com)
- [Capital Service S.A.](https://capitalservice.pl) [@pkonarzewski]
- [Clark.de](https://clark.de/)
- [Europace](https://europace.de)
- [KarrotPay](https://www.daangnpay.com/)
- [Remita](https://remita.net) [@mujibishola]
- [Taveo](https://www.taveo.com) [@codek]
- [Unit](https://www.unit.co/about-us) [@amitmiran137]
- [Wise](https://wise.com) [@koszti]
- [Xendit](https://xendit.co/) [@LieAlbertTriAdrian]
- [Cover Genius](https://covergenius.com/)
### Gaming
- [Popoko VM Games Studio](https://popoko.live)
### E-Commerce
- [AiHello](https://www.aihello.com) [@ganeshkrishnan1]
- [Bazaar Technologies](https://www.bazaartech.com) [@umair-abro]
- [Dragonpass](https://www.dragonpass.com.cn/) [@zhxjdwh]
- [Dropit Shopping](https://www.dropit.shop/) [@dropit-dev]
- [Fanatics](https://www.fanatics.com/) [@coderfender]
- [Fordeal](https://www.fordeal.com) [@Renkai]
- [Fynd](https://www.fynd.com/) [@darpanjain07]
- [GFG - Global Fashion Group](https://global-fashion-group.com) [@ksaagariconic]
- [GoTo/Gojek](https://www.gojek.io/) [@gwthm-in]
- [HuiShouBao](https://www.huishoubao.com/) [@Yukinoshita-Yukino]
- [Now](https://www.now.vn/) [@davidkohcw]
- [Qunar](https://www.qunar.com/) [@flametest]
- [Rakuten Viki](https://www.viki.com)
- [Shopee](https://shopee.sg) [@xiaohanyu]
- [Shopkick](https://www.shopkick.com) [@LAlbertalli]
- [ShopUp](https://www.shopup.org/) [@gwthm-in]
- [Tails.com](https://tails.com/gb/) [@alanmcruickshank]
- [THE ICONIC](https://theiconic.com.au/) [@ksaagariconic]
- [Utair](https://www.utair.ru) [@utair-digital]
- [VkusVill](https://vkusvill.ru/) [@ETselikov]
- [Zalando](https://www.zalando.com) [@dmigo]
- [Zalora](https://www.zalora.com) [@ksaagariconic]
- [Zepto](https://www.zeptonow.com/) [@gwthm-in]
### Enterprise Technology
- [A3Data](https://a3data.com.br) [@neylsoncrepalde]
- [Analytics Aura](https://analyticsaura.com/) [@Analytics-Aura]
- [Apollo GraphQL](https://www.apollographql.com/) [@evans]
- [Astronomer](https://www.astronomer.io) [@ryw]
- [Avesta Technologies](https://avestatechnologies.com/) [@TheRum]
- [Caizin](https://caizin.com/) [@tejaskatariya]
- [Canonical](https://canonical.com)
- [Careem](https://www.careem.com/) [@samraHanif0340]
- [Cloudsmith](https://cloudsmith.io) [@alancarson]
- [Cyberhaven](https://www.cyberhaven.com/) [@toliver-ch]
- [Deepomatic](https://deepomatic.com/) [@Zanoellia]
- [Dial Once](https://www.dial-once.com/)
- [Dremio](https://dremio.com) [@narendrans]
- [EFinance](https://www.efinance.com.eg) [@habeeb556]
- [Elestio](https://elest.io/) [@kaiwalyakoparkar]
- [ELMO Cloud HR & Payroll](https://elmosoftware.com.au/)
- [Endress+Hauser](https://www.endress.com/) [@rumbin]
- [FBK - ICT center](https://ict.fbk.eu)
- [Formbricks](https://formbricks.com)
- [Gavagai](https://gavagai.io) [@gavagai-corp]
- [GfK Data Lab](https://www.gfk.com/home) [@mherr]
- [HPE](https://www.hpe.com/in/en/home.html) [@anmol-hpe]
- [Hydrolix](https://www.hydrolix.io/)
- [Intercom](https://www.intercom.com/) [@kate-gallo]
- [jampp](https://jampp.com/)
- [Konfío](https://konfio.mx) [@uis-rodriguez]
- [Mainstrat](https://mainstrat.com/)
- [mishmash io](https://mishmash.io/) [@mishmash-io]
- [Myra Labs](https://www.myralabs.com/) [@viksit]
- [Nielsen](https://www.nielsen.com/) [@amitNielsen]
- [Ona](https://ona.io) [@pld]
- [Orange](https://www.orange.com) [@icsu]
- [Oslandia](https://oslandia.com)
- [Oxylabs](https://oxylabs.io/) [@rytis-ulys]
- [Peak AI](https://www.peak.ai/) [@azhar22k]
- [PeopleDoc](https://www.people-doc.com) [@rodo]
- [PlaidCloud](https://www.plaidcloud.com)
- [Preset, Inc.](https://preset.io)
- [PubNub](https://pubnub.com) [@jzucker2]
- [ReadyTech](https://www.readytech.io)
- [Reward Gateway](https://www.rewardgateway.com)
- [RIADVICE](https://riadvice.tn) [@riadvice]
- [ScopeAI](https://www.getscopeai.com) [@iloveluce]
- [shipmnts](https://shipmnts.com)
- [Showmax](https://showmax.com) [@bobek]
- [SingleStore](https://www.singlestore.com/)
- [TechAudit](https://www.techaudit.info) [@ETselikov]
- [Tenable](https://www.tenable.com) [@dflionis]
- [Tentacle](https://www.linkedin.com/company/tentacle-cmi/) [@jdclarke5]
- [timbr.ai](https://timbr.ai/) [@semantiDan]
- [Tobii](https://www.tobii.com/) [@dwa]
- [Tooploox](https://www.tooploox.com/) [@jakubczaplicki]
- [Unvired](https://unvired.com) [@srinisubramanian]
- [Virtuoso QA](https://www.virtuosoqa.com)
- [Whale](https://whale.im)
- [Windsor.ai](https://www.windsor.ai/) [@octaviancorlade]
- [WinWin Network马上赢](https://brandct.cn/) [@wenbinye]
- [Zeta](https://www.zeta.tech/) [@shaikidris]
### Media & Entertainment
- [6play](https://www.6play.fr) [@CoryChaplin]
- [bilibili](https://www.bilibili.com) [@Moinheart]
- [BurdaForward](https://www.burda-forward.de/en/)
- [Douban](https://www.douban.com/) [@luchuan]
- [Kuaishou](https://www.kuaishou.com/) [@zhaoyu89730105]
- [Netflix](https://www.netflix.com/)
- [Prensa Iberica](https://www.prensaiberica.es/) [@zamar-roura]
- [TME QQMUSIC/WESING](https://www.tencentmusic.com/) [@shenyuanli,@marklaw]
- [Xite](https://xite.com/) [@shashankkoppar]
- [Zaihang](https://www.zaih.com/)
### Education
- [Aveti Learning](https://avetilearning.com/) [@TheShubhendra]
- [Brilliant.org](https://brilliant.org/)
- [Open edX](https://openedx.org/)
- [Platzi.com](https://platzi.com/)
- [Sunbird](https://www.sunbird.org/) [@eksteporg]
- [The GRAPH Network](https://thegraphnetwork.org/) [@fccoelho]
- [Udemy](https://www.udemy.com/) [@sungjuly]
- [VIPKID](https://www.vipkid.com.cn/) [@illpanda]
- [WikiMedia Foundation](https://wikimediafoundation.org) [@vg]
### Energy
- [Airboxlab](https://foobot.io) [@antoine-galataud]
- [DouroECI](https://www.douroeci.com/) [@nunohelibeires]
- [Safaricom](https://www.safaricom.co.ke/) [@mmutiso]
- [Scoot](https://scoot.co/) [@haaspt]
- [Wattbewerb](https://wattbewerb.de/) [@wattbewerb]
### Healthcare
- [Amino](https://amino.com) [@shkr]
- [Bluesquare](https://www.bluesquarehub.com/) [@madewulf]
- [Care](https://www.getcare.io/) [@alandao2021]
- [Living Goods](https://www.livinggoods.org) [@chelule]
- [Maieutical Labs](https://maieuticallabs.it) [@xrmx]
- [Medic](https://medic.org) [@1yuv]
- [REDCap Cloud](https://www.redcapcloud.com/)
- [TrustMedis](https://trustmedis.com/) [@famasya]
- [WeSure](https://www.wesure.cn/)
- [2070Health](https://2070health.com/)
### HR / Staffing
- [Swile](https://www.swile.co/) [@PaoloTerzi]
- [Symmetrics](https://www.symmetrics.fyi)
- [bluquist](https://bluquist.com/)
### Government
- [City of Ann Arbor, MI](https://www.a2gov.org/) [@sfirke]
- [RIS3 Strategy of CZ, MIT CR](https://www.ris3.cz/) [@RIS3CZ]
- [NRLM - Sarathi, India](https://pib.gov.in/PressReleasePage.aspx?PRID=1999586)
### Travel
- [Agoda](https://www.agoda.com/) [@lostseaway, @maiake, @obombayo]
- [HomeToGo](https://hometogo.com/) [@pedromartinsteenstrup]
- [Skyscanner](https://www.skyscanner.net/) [@cleslie, @stanhoucke]
### Others
- [10Web](https://10web.io/)
- [AI inside](https://inside.ai/en/)
- [Automattic](https://automattic.com/) [@Khrol, @Usiel]
- [Dropbox](https://www.dropbox.com/) [@bkyryliuk]
- [Flowbird](https://flowbird.com) [@EmmanuelCbd]
- [GEOTAB](https://www.geotab.com) [@JZ6]
- [Grassroot](https://www.grassrootinstitute.org/)
- [Increff](https://www.increff.com/) [@ishansinghania]
- [komoot](https://www.komoot.com/) [@christophlingg]
- [Let's Roam](https://www.letsroam.com/)
- [Machrent SA](https://www.machrent.com/)
- [Onebeat](https://1beat.com/) [@GuyAttia]
- [X](https://x.com/)
- [VLMedia](https://www.vlmedia.com.tr/) [@ibotheperfect]
- [Yahoo!](https://yahoo.com/)

View File

@@ -1,653 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Apache Superset Users in the Wild
#
# To add your organization:
# 1. Find the appropriate category (or add a new one)
# 2. Add an entry with your organization details
# 3. Optionally add a logo file to docs/static/img/logos/
#
# Required fields:
# - name: Your organization name
# - url: Link to your organization's website
#
# Optional fields:
# - logo: Filename of logo in docs/static/img/logos/ (e.g., "mycompany.svg")
# - contributors: List of GitHub usernames who contributed (e.g., ["@username"])
categories:
Sharing Economy:
- name: Airbnb
url: https://github.com/airbnb
- name: Faasos
url: https://faasos.com/
contributors: ["@shashanksingh"]
- name: Free2Move
url: https://www.free2move.com/
contributors: ["@PaoloTerzi"]
- name: Hostnfly
url: https://www.hostnfly.com/
contributors: ["@alexisrosuel"]
- name: Lime
url: https://www.li.me/
contributors: ["@cxmcc"]
- name: Lyft
url: https://www.lyft.com/
- name: Ontruck
url: https://www.ontruck.com/
Financial Services:
- name: Aktia Bank plc
url: https://www.aktia.com
- name: American Express
url: https://www.americanexpress.com
contributors: ["@TheLastSultan"]
- name: bumper
url: https://www.bumper.co/
contributors: ["@vasu-ram", "@JamiePercival"]
- name: Cape Crypto
url: https://capecrypto.com
- name: Capital Service S.A.
url: https://capitalservice.pl
contributors: ["@pkonarzewski"]
- name: Clark.de
url: https://clark.de/
- name: EnquiryLabs
url: https://www.enquirylabs.co.uk
- name: Europace
url: https://europace.de
- name: KarrotPay
url: https://www.daangnpay.com/
- name: Remita
url: https://remita.net
contributors: ["@mujibishola"]
- name: Taveo
url: https://www.taveo.com
contributors: ["@codek"]
- name: Unit
url: https://www.unit.co/about-us
contributors: ["@amitmiran137"]
- name: Wise
url: https://wise.com
contributors: ["@koszti"]
- name: Xendit
url: https://xendit.co/
contributors: ["@LieAlbertTriAdrian"]
- name: Cover Genius
url: https://covergenius.com/
Gaming:
- name: Popoko VM Games Studio
url: https://popoko.live
E-Commerce:
- name: AiHello
url: https://www.aihello.com
contributors: ["@ganeshkrishnan1"]
- name: Bazaar Technologies
url: https://www.bazaartech.com
contributors: ["@umair-abro"]
- name: Dragonpass
url: https://www.dragonpass.com.cn/
contributors: ["@zhxjdwh"]
- name: Dropit Shopping
url: https://www.dropit.shop/
contributors: ["@dropit-dev"]
- name: Fanatics
url: https://www.fanatics.com/
contributors: ["@coderfender"]
- name: Fordeal
url: https://www.fordeal.com
contributors: ["@Renkai"]
- name: Fynd
url: https://www.fynd.com/
contributors: ["@darpanjain07"]
- name: GFG - Global Fashion Group
url: https://global-fashion-group.com
contributors: ["@ksaagariconic"]
- name: GoTo/Gojek
url: https://www.gojek.io/
contributors: ["@gwthm-in"]
- name: HuiShouBao
url: https://www.huishoubao.com/
contributors: ["@Yukinoshita-Yukino"]
- name: Now
url: https://www.now.vn/
contributors: ["@davidkohcw"]
- name: Qunar
url: https://www.qunar.com/
contributors: ["@flametest"]
- name: Rakuten Viki
url: https://www.viki.com
- name: Shopee
url: https://shopee.sg
contributors: ["@xiaohanyu"]
- name: Shopkick
url: https://www.shopkick.com
contributors: ["@LAlbertalli"]
- name: ShopUp
url: https://www.shopup.org/
contributors: ["@gwthm-in"]
- name: Tails.com
url: https://tails.com/gb/
contributors: ["@alanmcruickshank"]
- name: THE ICONIC
url: https://theiconic.com.au/
contributors: ["@ksaagariconic"]
- name: Utair
url: https://www.utair.ru
contributors: ["@utair-digital"]
- name: VkusVill
url: https://vkusvill.ru/
contributors: ["@ETselikov"]
- name: Zalando
url: https://www.zalando.com
contributors: ["@dmigo"]
- name: Zalora
url: https://www.zalora.com
contributors: ["@ksaagariconic"]
- name: Zepto
url: https://www.zeptonow.com/
contributors: ["@gwthm-in"]
Enterprise Technology:
- name: A3Data
url: https://a3data.com.br
contributors: ["@neylsoncrepalde"]
- name: Analytics Aura
url: https://analyticsaura.com/
contributors: ["@Analytics-Aura"]
- name: Apollo GraphQL
url: https://www.apollographql.com/
contributors: ["@evans"]
- name: Astronomer
url: https://www.astronomer.io
contributors: ["@ryw"]
- name: Avesta Technologies
url: https://avestatechnologies.com/
contributors: ["@TheRum"]
- name: Caizin
url: https://caizin.com/
contributors: ["@tejaskatariya"]
- name: Canonical
url: https://canonical.com
- name: Careem
url: https://www.careem.com/
contributors: ["@samraHanif0340"]
- name: Cloudsmith
url: https://cloudsmith.io
contributors: ["@alancarson"]
- name: Cyberhaven
url: https://www.cyberhaven.com/
contributors: ["@toliver-ch"]
- name: Deepomatic
url: https://deepomatic.com/
contributors: ["@Zanoellia"]
- name: Dial Once
url: https://www.dial-once.com/
- name: Dremio
url: https://dremio.com
contributors: ["@narendrans"]
- name: EFinance
url: https://www.efinance.com.eg
contributors: ["@habeeb556"]
- name: Elestio
url: https://elest.io/
contributors: ["@kaiwalyakoparkar"]
- name: ELMO Cloud HR & Payroll
url: https://elmosoftware.com.au/
- name: Endress+Hauser
url: https://www.endress.com/
contributors: ["@rumbin"]
- name: FBK - ICT center
url: https://ict.fbk.eu
- name: Formbricks
url: https://formbricks.com
- name: Gavagai
url: https://gavagai.io
contributors: ["@gavagai-corp"]
- name: GfK Data Lab
url: https://www.gfk.com/home
contributors: ["@mherr"]
- name: HPE
url: https://www.hpe.com/in/en/home.html
contributors: ["@anmol-hpe"]
- name: Hydrolix
url: https://www.hydrolix.io/
- name: Intercom
url: https://www.intercom.com/
contributors: ["@kate-gallo"]
- name: jampp
url: https://jampp.com/
- name: Konfío
url: https://konfio.mx
contributors: ["@uis-rodriguez"]
- name: Mainstrat
url: https://mainstrat.com/
- name: mishmash io
url: https://mishmash.io/
contributors: ["@mishmash-io"]
- name: Myra Labs
url: https://www.myralabs.com/
contributors: ["@viksit"]
- name: Nielsen
url: https://www.nielsen.com/
contributors: ["@amitNielsen"]
- name: Ona
url: https://ona.io
contributors: ["@pld"]
- name: Orange
url: https://www.orange.com
contributors: ["@icsu"]
- name: Oslandia
url: https://oslandia.com
- name: Oxylabs
url: https://oxylabs.io/
contributors: ["@rytis-ulys"]
- name: Peak AI
url: https://www.peak.ai/
contributors: ["@azhar22k"]
- name: PeopleDoc
url: https://www.people-doc.com
contributors: ["@rodo"]
- name: PlaidCloud
url: https://www.plaidcloud.com
- name: Preset, Inc.
url: https://preset.io
logo: preset.svg
contributors: ["@mistercrunch", "@betodealmeida", "@dpgaspar", "@rusackas", "@sadpandajoe", "@Vitor-Avila", "@kgabryje", "@geido", "@eschutho", "@Antonio-RiveroMartnez", "@yousoph"]
- name: PubNub
url: https://pubnub.com
contributors: ["@jzucker2"]
- name: ReadyTech
url: https://www.readytech.io
- name: Reward Gateway
url: https://www.rewardgateway.com
- name: RIADVICE
url: https://riadvice.tn
contributors: ["@riadvice"]
- name: ScopeAI
url: https://www.getscopeai.com
contributors: ["@iloveluce"]
- name: shipmnts
url: https://shipmnts.com
- name: Showmax
url: https://showmax.com
contributors: ["@bobek"]
- name: SingleStore
url: https://www.singlestore.com/
- name: TechAudit
url: https://www.techaudit.info
contributors: ["@ETselikov"]
- name: Tenable
url: https://www.tenable.com
contributors: ["@dflionis"]
- name: Tentacle
url: https://www.linkedin.com/company/tentacle-cmi/
contributors: ["@jdclarke5"]
- name: timbr.ai
url: https://timbr.ai/
contributors: ["@semantiDan"]
- name: Tobii
url: https://www.tobii.com/
contributors: ["@dwa"]
- name: Tooploox
url: https://www.tooploox.com/
contributors: ["@jakubczaplicki"]
- name: Unvired
url: https://unvired.com
contributors: ["@srinisubramanian"]
- name: Virtuoso QA
url: https://www.virtuosoqa.com
- name: Whale
url: https://whale.im
- name: Windsor.ai
url: https://www.windsor.ai/
contributors: ["@octaviancorlade"]
- name: WinWin Network马上赢
url: https://brandct.cn/
contributors: ["@wenbinye"]
- name: Zeta
url: https://www.zeta.tech/
contributors: ["@shaikidris"]
Media & Entertainment:
- name: 6play
url: https://www.6play.fr
contributors: ["@CoryChaplin"]
- name: bilibili
url: https://www.bilibili.com
contributors: ["@Moinheart"]
- name: BurdaForward
url: https://www.burda-forward.de/en/
- name: Douban
url: https://www.douban.com/
contributors: ["@luchuan"]
- name: Kuaishou
url: https://www.kuaishou.com/
contributors: ["@zhaoyu89730105"]
- name: Netflix
url: https://www.netflix.com/
- name: Prensa Iberica
url: https://www.prensaiberica.es/
contributors: ["@zamar-roura"]
- name: TME QQMUSIC/WESING
url: https://www.tencentmusic.com/
contributors: ["@shenyuanli", "@marklaw"]
- name: Xite
url: https://xite.com/
contributors: ["@shashankkoppar"]
- name: Zaihang
url: https://www.zaih.com/
Education:
- name: Aveti Learning
url: https://avetilearning.com/
contributors: ["@TheShubhendra"]
- name: Brilliant.org
url: https://brilliant.org/
- name: Open edX
url: https://openedx.org/
- name: Platzi.com
url: https://platzi.com/
- name: Sunbird
url: https://www.sunbird.org/
contributors: ["@eksteporg"]
- name: The GRAPH Network
url: https://thegraphnetwork.org/
contributors: ["@fccoelho"]
- name: Udemy
url: https://www.udemy.com/
contributors: ["@sungjuly"]
- name: VIPKID
url: https://www.vipkid.com.cn/
contributors: ["@illpanda"]
- name: WikiMedia Foundation
url: https://wikimediafoundation.org
contributors: ["@vg"]
Energy:
- name: Airboxlab
url: https://foobot.io
contributors: ["@antoine-galataud"]
- name: DouroECI
url: https://www.douroeci.com/
contributors: ["@nunohelibeires"]
- name: Safaricom
url: https://www.safaricom.co.ke/
contributors: ["@mmutiso"]
- name: Scoot
url: https://scoot.co/
contributors: ["@haaspt"]
- name: Wattbewerb
url: https://wattbewerb.de/
contributors: ["@wattbewerb"]
- name: Rogow
url: https://rogow.com.br/
contributors: ["@nilmonto"]
Healthcare:
- name: Amino
url: https://amino.com
contributors: ["@shkr"]
- name: Bluesquare
url: https://www.bluesquarehub.com/
contributors: ["@madewulf"]
- name: Care
url: https://www.getcare.io/
contributors: ["@alandao2021"]
- name: Living Goods
url: https://www.livinggoods.org
contributors: ["@chelule"]
- name: Maieutical Labs
url: https://maieuticallabs.it
contributors: ["@xrmx"]
- name: Medic
url: https://medic.org
contributors: ["@1yuv"]
- name: REDCap Cloud
url: https://www.redcapcloud.com/
- name: TrustMedis
url: https://trustmedis.com/
contributors: ["@famasya"]
- name: WeSure
url: https://www.wesure.cn/
- name: 2070Health
url: https://2070health.com/
HR / Staffing:
- name: Swile
url: https://www.swile.co/
contributors: ["@PaoloTerzi"]
- name: Symmetrics
url: https://www.symmetrics.fyi
- name: bluquist
url: https://bluquist.com/
Government:
- name: City of Ann Arbor, MI
url: https://www.a2gov.org/
contributors: ["@sfirke"]
- name: RIS3 Strategy of CZ, MIT CR
url: https://www.ris3.cz/
contributors: ["@RIS3CZ"]
- name: NRLM - Sarathi, India
url: https://pib.gov.in/PressReleasePage.aspx?PRID=1999586
Mobile Software:
- name: VLMedia
url: https://www.vlmedia.com.tr
logo: vlmedia.svg
contributors: ["@iercan"]
Travel:
- name: Agoda
url: https://www.agoda.com/
contributors: ["@lostseaway", "@maiake", "@obombayo"]
- name: HomeToGo
url: https://hometogo.com/
contributors: ["@pedromartinsteenstrup"]
- name: Skyscanner
url: https://www.skyscanner.net/
contributors: ["@cleslie", "@stanhoucke"]
Others:
- name: 10Web
url: https://10web.io/
- name: AI inside
url: https://inside.ai/en/
- name: Automattic
url: https://automattic.com/
contributors: ["@Khrol", "@Usiel"]
- name: Dropbox
url: https://www.dropbox.com/
contributors: ["@bkyryliuk"]
- name: Flowbird
url: https://flowbird.com
contributors: ["@EmmanuelCbd"]
- name: GEOTAB
url: https://www.geotab.com
contributors: ["@JZ6"]
- name: Grassroot
url: https://www.grassrootinstitute.org/
- name: Increff
url: https://www.increff.com/
contributors: ["@ishansinghania"]
- name: komoot
url: https://www.komoot.com/
contributors: ["@christophlingg"]
- name: Let's Roam
url: https://www.letsroam.com/
- name: Machrent SA
url: https://www.machrent.com/
- name: Onebeat
url: https://1beat.com/
contributors: ["@GuyAttia"]
- name: X
url: https://x.com/
- name: Yahoo!
url: https://yahoo.com/

View File

@@ -17,193 +17,192 @@ specific language governing permissions and limitations
under the License.
-->
| |Admin|Alpha|Gamma|Public|SQL_LAB|
|--------------------------------------------------|---|---|---|---|---|
| Permission/role description |Admins have all possible rights, including granting or revoking rights from other users and altering other people's slices and dashboards.|Alpha users have access to all data sources, but they cannot grant or revoke access from other users. They are also limited to altering the objects that they own. Alpha users can add and alter data sources.|Gamma users have limited access. They can only consume data coming from data sources they have been given access to through another complementary role. They only have access to view the slices and dashboards made from data sources that they have access to. Currently Gamma users are not able to alter or add data sources. We assume that they are mostly content consumers, though they can create slices and dashboards.|Public is the most restrictive built-in role, designed for anonymous/unauthenticated users viewing public dashboards. It provides minimal read-only access for dashboard viewing with interactive filters. Use `PUBLIC_ROLE_LIKE = "Public"` to apply these permissions to anonymous users.|The sql_lab role grants access to SQL Lab. Note that while Admin users have access to all databases by default, both Alpha and Gamma users need to be given access on a per database basis.||
| can read on SavedQuery |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can write on SavedQuery |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can read on CssTemplate |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can write on CssTemplate |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can read on ReportSchedule |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can write on ReportSchedule |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can read on Chart |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can write on Chart |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can read on Annotation |:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|O|
| can write on Annotation |:heavy_check_mark:|:heavy_check_mark:|O|O|O|
| can read on AnnotationLayerRestApi |:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|O|
| can read on Dataset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can write on Dataset |:heavy_check_mark:|:heavy_check_mark:|O|O|O|
| can read on Log |:heavy_check_mark:|O|O|O|O|
| can write on Log |:heavy_check_mark:|O|O|O|O|
| can read on Dashboard |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can write on Dashboard |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can read on Database |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can write on Database |:heavy_check_mark:|O|O|O|O|
| can read on Query |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can this form get on ResetPasswordView |:heavy_check_mark:|O|O|O|O|
| can this form post on ResetPasswordView |:heavy_check_mark:|O|O|O|O|
| can this form get on ResetMyPasswordView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can this form post on ResetMyPasswordView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can this form get on UserInfoEditView |:heavy_check_mark:|O|O|O|O|
| can this form post on UserInfoEditView |:heavy_check_mark:|O|O|O|O|
| can show on UserDBModelView |:heavy_check_mark:|O|O|O|O|
| can edit on UserDBModelView |:heavy_check_mark:|O|O|O|O|
| can delete on UserDBModelView |:heavy_check_mark:|O|O|O|O|
| can add on UserDBModelView |:heavy_check_mark:|O|O|O|O|
| can list on UserDBModelView |:heavy_check_mark:|O|O|O|O|
| can userinfo on UserDBModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| resetmypassword on UserDBModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| resetpasswords on UserDBModelView |:heavy_check_mark:|O|O|O|O|
| userinfoedit on UserDBModelView |:heavy_check_mark:|O|O|O|O|
| can show on RoleModelView |:heavy_check_mark:|O|O|O|O|
| can edit on RoleModelView |:heavy_check_mark:|O|O|O|O|
| can delete on RoleModelView |:heavy_check_mark:|O|O|O|O|
| can add on RoleModelView |:heavy_check_mark:|O|O|O|O|
| can list on RoleModelView |:heavy_check_mark:|O|O|O|O|
| copyrole on RoleModelView |:heavy_check_mark:|O|O|O|O|
| can get on OpenApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can show on SwaggerView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can get on MenuApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can list on AsyncEventsRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can invalidate on CacheRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can csv upload on Database |:heavy_check_mark:|O|O|O|O|
| can excel upload on Database |:heavy_check_mark:|O|O|O|O|
| can query form data on Api |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can query on Api |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can time range on Api |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can external metadata on Datasource |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can save on Datasource |:heavy_check_mark:|:heavy_check_mark:|O|O|O|
| can get on Datasource |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can my queries on SqlLab |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can log on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can import dashboards on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can schemas on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can sqllab history on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can publish on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can csv on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can slice on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can sync druid source on Superset |:heavy_check_mark:|O|O|O|O|
| can explore on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can approve on Superset |:heavy_check_mark:|O|O|O|O|
| can explore json on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can fetch datasource metadata on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can csrf token on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can sqllab on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can select star on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can warm up cache on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can sqllab table viz on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can available domains on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can request access on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can dashboard on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can post on TableSchemaView |:heavy_check_mark:|O|O|O|:heavy_check_mark:|
| can expanded on TableSchemaView |:heavy_check_mark:|O|O|O|:heavy_check_mark:|
| can delete on TableSchemaView |:heavy_check_mark:|O|O|O|:heavy_check_mark:|
| can get on TabStateView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can post on TabStateView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can delete query on TabStateView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can migrate query on TabStateView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can activate on TabStateView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can delete on TabStateView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can put on TabStateView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can read on SecurityRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| menu access on Security |:heavy_check_mark:|O|O|O|O|
| menu access on List Users |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on List Roles |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Action Log |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Manage |:heavy_check_mark:|:heavy_check_mark:|O|O|O|
| menu access on Annotation Layers |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on CSS Templates |:heavy_check_mark:|:heavy_check_mark:|O|O|O|
| menu access on Import Dashboards |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Data |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Databases |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Datasets |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Charts |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Dashboards |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on SQL Lab |:heavy_check_mark:|O|O|O|:heavy_check_mark:|
| menu access on SQL Editor |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| menu access on Saved Queries |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| menu access on Query Search |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| all datasource access on all_datasource_access |:heavy_check_mark:|:heavy_check_mark:|O|O|O|
| all database access on all_database_access |:heavy_check_mark:|:heavy_check_mark:|O|O|O|
| all query access on all_query_access |:heavy_check_mark:|O|O|O|O|
| can write on DynamicPlugin |:heavy_check_mark:|O|O|O|O|
| can edit on DynamicPlugin |:heavy_check_mark:|O|O|O|O|
| can list on DynamicPlugin |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can show on DynamicPlugin |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can download on DynamicPlugin |:heavy_check_mark:|O|O|O|O|
| can add on DynamicPlugin |:heavy_check_mark:|O|O|O|O|
| can delete on DynamicPlugin |:heavy_check_mark:|O|O|O|O|
| can external metadata by name on Datasource |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can get value on KV |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can store on KV |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can tagged objects on TagView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can suggestions on TagView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can get on TagView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can post on TagView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can delete on TagView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can edit on DashboardEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can list on DashboardEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can show on DashboardEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can add on DashboardEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can delete on DashboardEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| muldelete on DashboardEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|O|O|O|
| can edit on SliceEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can list on SliceEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can show on SliceEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can add on SliceEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can delete on SliceEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| muldelete on SliceEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|O|O|O|
| can edit on AlertModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can list on AlertModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can show on AlertModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can add on AlertModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can delete on AlertModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can list on AlertLogModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can show on AlertLogModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can list on AlertObservationModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can show on AlertObservationModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Row Level Security |:heavy_check_mark:|O|O|O|O|
| menu access on Access requests |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Home |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Plugins |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Dashboard Email Schedules |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Chart Emails |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Alerts |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Alerts & Report |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Scan New Datasources |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can share dashboard on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can share chart on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can this form get on ColumnarToDatabaseView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can this form post on ColumnarToDatabaseView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can export on Chart |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can write on DashboardFilterStateRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can read on DashboardFilterStateRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can write on DashboardPermalinkRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can read on DashboardPermalinkRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can delete embedded on Dashboard |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can set embedded on Dashboard |:heavy_check_mark:|O|O|O|O|
| can export on Dashboard |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can get embedded on Dashboard |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can export on Database |:heavy_check_mark:|O|O|O|O|
| can export on Dataset |:heavy_check_mark:|:heavy_check_mark:|O|O|O|
| can write on ExploreFormDataRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can read on ExploreFormDataRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can write on ExplorePermalinkRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can read on ExplorePermalinkRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can export on ImportExportRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can import on ImportExportRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can export on SavedQuery |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|:heavy_check_mark:|
| can dashboard permalink on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can grant guest token on SecurityRestApi |:heavy_check_mark:|O|O|O|O|
| can read on AdvancedDataType |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can read on EmbeddedDashboard |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can duplicate on Dataset |:heavy_check_mark:|:heavy_check_mark:|O|O|O|
| can read on Explore |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can samples on Datasource |:heavy_check_mark:|:heavy_check_mark:|O|O|O|
| can read on AvailableDomains |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| can get or create dataset on Dataset |:heavy_check_mark:|:heavy_check_mark:|O|O|O|
| can get column values on Datasource |:heavy_check_mark:|:heavy_check_mark:|O|O|O|
| can export csv on SQLLab |:heavy_check_mark:|O|O|O|:heavy_check_mark:|
| can get results on SQLLab |:heavy_check_mark:|O|O|O|:heavy_check_mark:|
| can execute sql query on SQLLab |:heavy_check_mark:|O|O|O|:heavy_check_mark:|
| can recent activity on Log |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|O|
| |Admin|Alpha|Gamma|SQL_LAB|
|--------------------------------------------------|---|---|---|---|
| Permission/role description |Admins have all possible rights, including granting or revoking rights from other users and altering other peoples slices and dashboards.|Alpha users have access to all data sources, but they cannot grant or revoke access from other users. They are also limited to altering the objects that they own. Alpha users can add and alter data sources.|Gamma users have limited access. They can only consume data coming from data sources they have been given access to through another complementary role. They only have access to view the slices and dashboards made from data sources that they have access to. Currently Gamma users are not able to alter or add data sources. We assume that they are mostly content consumers, though they can create slices and dashboards.|The sql_lab role grants access to SQL Lab. Note that while Admin users have access to all databases by default, both Alpha and Gamma users need to be given access on a per database basis.||
| can read on SavedQuery |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can write on SavedQuery |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can read on CssTemplate |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can write on CssTemplate |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can read on ReportSchedule |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can write on ReportSchedule |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can read on Chart |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can write on Chart |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can read on Annotation |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can write on Annotation |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can read on Dataset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can write on Dataset |:heavy_check_mark:|:heavy_check_mark:|O|O|
| can read on Log |:heavy_check_mark:|O|O|O|
| can write on Log |:heavy_check_mark:|O|O|O|
| can read on Dashboard |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can write on Dashboard |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can read on Database |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can write on Database |:heavy_check_mark:|O|O|O|
| can read on Query |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can this form get on ResetPasswordView |:heavy_check_mark:|O|O|O|
| can this form post on ResetPasswordView |:heavy_check_mark:|O|O|O|
| can this form get on ResetMyPasswordView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can this form post on ResetMyPasswordView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can this form get on UserInfoEditView |:heavy_check_mark:|O|O|O|
| can this form post on UserInfoEditView |:heavy_check_mark:|O|O|O|
| can show on UserDBModelView |:heavy_check_mark:|O|O|O|
| can edit on UserDBModelView |:heavy_check_mark:|O|O|O|
| can delete on UserDBModelView |:heavy_check_mark:|O|O|O|
| can add on UserDBModelView |:heavy_check_mark:|O|O|O|
| can list on UserDBModelView |:heavy_check_mark:|O|O|O|
| can userinfo on UserDBModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| resetmypassword on UserDBModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| resetpasswords on UserDBModelView |:heavy_check_mark:|O|O|O|
| userinfoedit on UserDBModelView |:heavy_check_mark:|O|O|O|
| can show on RoleModelView |:heavy_check_mark:|O|O|O|
| can edit on RoleModelView |:heavy_check_mark:|O|O|O|
| can delete on RoleModelView |:heavy_check_mark:|O|O|O|
| can add on RoleModelView |:heavy_check_mark:|O|O|O|
| can list on RoleModelView |:heavy_check_mark:|O|O|O|
| copyrole on RoleModelView |:heavy_check_mark:|O|O|O|
| can get on OpenApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can show on SwaggerView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can get on MenuApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can list on AsyncEventsRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can invalidate on CacheRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can csv upload on Database |:heavy_check_mark:|O|O|O|
| can excel upload on Database |:heavy_check_mark:|O|O|O|
| can query form data on Api |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can query on Api |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can time range on Api |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can external metadata on Datasource |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can save on Datasource |:heavy_check_mark:|:heavy_check_mark:|O|O|
| can get on Datasource |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can my queries on SqlLab |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can log on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can import dashboards on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can schemas on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can sqllab history on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can publish on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can csv on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can slice on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can sync druid source on Superset |:heavy_check_mark:|O|O|O|
| can explore on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can approve on Superset |:heavy_check_mark:|O|O|O|
| can explore json on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can fetch datasource metadata on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can csrf token on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can sqllab on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can select star on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can warm up cache on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can sqllab table viz on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can available domains on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can request access on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can dashboard on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can post on TableSchemaView |:heavy_check_mark:|O|O|:heavy_check_mark:|
| can expanded on TableSchemaView |:heavy_check_mark:|O|O|:heavy_check_mark:|
| can delete on TableSchemaView |:heavy_check_mark:|O|O|:heavy_check_mark:|
| can get on TabStateView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can post on TabStateView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can delete query on TabStateView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can migrate query on TabStateView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can activate on TabStateView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can delete on TabStateView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can put on TabStateView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can read on SecurityRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| menu access on Security |:heavy_check_mark:|O|O|O|
| menu access on List Users |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on List Roles |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Action Log |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Manage |:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Annotation Layers |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on CSS Templates |:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Import Dashboards |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Data |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Databases |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Datasets |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Charts |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Dashboards |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on SQL Lab |:heavy_check_mark:|O|O|:heavy_check_mark:|
| menu access on SQL Editor |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| menu access on Saved Queries |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| menu access on Query Search |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| all datasource access on all_datasource_access |:heavy_check_mark:|:heavy_check_mark:|O|O|
| all database access on all_database_access |:heavy_check_mark:|:heavy_check_mark:|O|O|
| all query access on all_query_access |:heavy_check_mark:|O|O|O|
| can write on DynamicPlugin |:heavy_check_mark:|O|O|O|
| can edit on DynamicPlugin |:heavy_check_mark:|O|O|O|
| can list on DynamicPlugin |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can show on DynamicPlugin |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can download on DynamicPlugin |:heavy_check_mark:|O|O|O|
| can add on DynamicPlugin |:heavy_check_mark:|O|O|O|
| can delete on DynamicPlugin |:heavy_check_mark:|O|O|O|
| can external metadata by name on Datasource |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can get value on KV |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can store on KV |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can tagged objects on TagView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can suggestions on TagView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can get on TagView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can post on TagView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can delete on TagView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can edit on DashboardEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can list on DashboardEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can show on DashboardEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can add on DashboardEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can delete on DashboardEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| muldelete on DashboardEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|O|O|
| can edit on SliceEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can list on SliceEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can show on SliceEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can add on SliceEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can delete on SliceEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| muldelete on SliceEmailScheduleView |:heavy_check_mark:|:heavy_check_mark:|O|O|
| can edit on AlertModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can list on AlertModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can show on AlertModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can add on AlertModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can delete on AlertModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can list on AlertLogModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can show on AlertLogModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can list on AlertObservationModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can show on AlertObservationModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Row Level Security |:heavy_check_mark:|O|O|O|
| menu access on Access requests |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Home |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Plugins |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Dashboard Email Schedules |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Chart Emails |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Alerts |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Alerts & Report |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Scan New Datasources |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can share dashboard on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can share chart on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can this form get on ColumnarToDatabaseView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can this form post on ColumnarToDatabaseView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can export on Chart |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can write on DashboardFilterStateRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can read on DashboardFilterStateRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can write on DashboardPermalinkRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can read on DashboardPermalinkRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can delete embedded on Dashboard |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can set embedded on Dashboard |:heavy_check_mark:|O|O|O|
| can export on Dashboard |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can get embedded on Dashboard |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can export on Database |:heavy_check_mark:|O|O|O|
| can export on Dataset |:heavy_check_mark:|:heavy_check_mark:|O|O|
| can write on ExploreFormDataRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can read on ExploreFormDataRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can write on ExplorePermalinkRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can read on ExplorePermalinkRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can export on ImportExportRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can import on ImportExportRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can export on SavedQuery |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can dashboard permalink on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can grant guest token on SecurityRestApi |:heavy_check_mark:|O|O|O|
| can read on AdvancedDataType |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can read on EmbeddedDashboard |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can duplicate on Dataset |:heavy_check_mark:|:heavy_check_mark:|O|O|
| can read on Explore |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can samples on Datasource |:heavy_check_mark:|:heavy_check_mark:|O|O|
| can read on AvailableDomains |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can get or create dataset on Dataset |:heavy_check_mark:|:heavy_check_mark:|O|O|
| can get column values on Datasource |:heavy_check_mark:|:heavy_check_mark:|O|O|
| can export csv on SQLLab |:heavy_check_mark:|O|O|:heavy_check_mark:|
| can get results on SQLLab |:heavy_check_mark:|O|O|:heavy_check_mark:|
| can execute sql query on SQLLab |:heavy_check_mark:|O|O|:heavy_check_mark:|
| can recent activity on Log |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|

View File

@@ -125,30 +125,8 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
---
- [35621](https://github.com/apache/superset/pull/35621): The default hash algorithm has changed from MD5 to SHA-256 for improved security and FedRAMP compliance. This affects cache keys for thumbnails, dashboard digests, chart digests, and filter option names. Existing cached data will be invalidated upon upgrade. To opt out of this change and maintain backward compatibility, set `HASH_ALGORITHM = "md5"` in your `superset_config.py`.
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
### Breaking Changes
- [36317](https://github.com/apache/superset/pull/36317): The `CUSTOM_FONT_URLS` configuration option has been removed. Use the new per-theme `fontUrls` token in `THEME_DEFAULT` or database-managed themes instead.
- **Before:**
```python
CUSTOM_FONT_URLS = [
"https://fonts.example.com/myfont.css",
]
```
- **After:**
```python
THEME_DEFAULT = {
"token": {
"fontUrls": [
"https://fonts.example.com/myfont.css",
],
# ... other tokens
}
}
```
## 6.0.0
- [33055](https://github.com/apache/superset/pull/33055): Upgrades Flask-AppBuilder to 5.0.0. The AUTH_OID authentication type has been deprecated and is no longer available as an option in Flask-AppBuilder. OpenID (OID) is considered a deprecated authentication protocol - if you are using AUTH_OID, you will need to migrate to an alternative authentication method such as OAuth, LDAP, or database authentication before upgrading.
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
- [34871](https://github.com/apache/superset/pull/34871): Fixed Jest test hanging issue from Ant Design v5 upgrade. MessageChannel is now mocked in test environment to prevent rc-overflow from causing Jest to hang. Test environment only - no production impact.
- [34782](https://github.com/apache/superset/pull/34782): Dataset exports now include the dataset ID in their file name (similar to charts and dashboards). If managing assets as code, make sure to rename existing dataset YAMLs to include the ID (and avoid duplicated files).
- [34536](https://github.com/apache/superset/pull/34536): The `ENVIRONMENT_TAG_CONFIG` color values have changed to support only Ant Design semantic colors. Update your `superset_config.py`:
@@ -165,10 +143,35 @@ Note: Pillow is now a required dependency (previously optional) to support image
- [33116](https://github.com/apache/superset/pull/33116) In Echarts Series charts (e.g. Line, Area, Bar, etc.) charts, the `x_axis_sort_series` and `x_axis_sort_series_ascending` form data items have been renamed with `x_axis_sort` and `x_axis_sort_asc`.
There's a migration added that can potentially affect a significant number of existing charts.
- [32317](https://github.com/apache/superset/pull/32317) The horizontal filter bar feature is now out of testing/beta development and its feature flag `HORIZONTAL_FILTER_BAR` has been removed.
- [31590](https://github.com/apache/superset/pull/31590) Marks the begining of intricate work around supporting dynamic Theming, and breaks support for [THEME_OVERRIDES](https://github.com/apache/superset/blob/732de4ac7fae88e29b7f123b6cbb2d7cd411b0e4/superset/config.py#L671) in favor of a new theming system based on AntD V5. Likely this will be in disrepair until settling over the 5.x lifecycle.
- [31590](https://github.com/apache/superset/pull/31590) Marks the beginning of intricate work around supporting dynamic Theming, and breaks support for [THEME_OVERRIDES](https://github.com/apache/superset/blob/732de4ac7fae88e29b7f123b6cbb2d7cd411b0e4/superset/config.py#L671) in favor of a new theming system based on AntD V5. Likely this will be in disrepair until settling over the 5.x lifecycle.
- [32432](https://github.com/apache/superset/pull/31260) Moves the List Roles FAB view to the frontend and requires `FAB_ADD_SECURITY_API` to be enabled in the configuration and `superset init` to be executed.
- [34319](https://github.com/apache/superset/pull/34319) Drill to Detail and Drill By is now supported in Embedded mode, and also with the `DASHBOARD_RBAC` FF. If you don't want to expose these features in Embedded / `DASHBOARD_RBAC`, make sure the roles used for Embedded / `DASHBOARD_RBAC`don't have the required permissions to perform D2D actions.
### Breaking Changes
#### CUSTOM_FONT_URLS removed
The `CUSTOM_FONT_URLS` configuration option has been removed. Use the new per-theme `fontUrls` token in `THEME_DEFAULT` or database-managed themes instead.
**Before (5.x):**
```python
CUSTOM_FONT_URLS = [
"https://fonts.example.com/myfont.css",
]
```
**After (6.0):**
```python
THEME_DEFAULT = {
"token": {
"fontUrls": [
"https://fonts.example.com/myfont.css",
],
# ... other tokens
}
}
```
## 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.

View File

@@ -54,9 +54,10 @@ services:
- path: docker/.env-local # optional override
required: false
image: nginx:latest
container_name: superset_nginx
restart: unless-stopped
ports:
- "${NGINX_PORT:-80}:80"
- "80:80"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
@@ -65,9 +66,10 @@ services:
redis:
image: redis:7
container_name: superset_cache
restart: unless-stopped
ports:
- "127.0.0.1:${REDIS_PORT:-6379}:6379"
- "127.0.0.1:6379:6379"
volumes:
- redis:/data
@@ -78,9 +80,10 @@ services:
- path: docker/.env-local # optional override
required: false
image: postgres:16
container_name: superset_db
restart: unless-stopped
ports:
- "127.0.0.1:${DATABASE_PORT:-5432}:5432"
- "127.0.0.1:5432:5432"
volumes:
- db_home:/var/lib/postgresql/data
- ./docker/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
@@ -93,12 +96,13 @@ services:
required: false
build:
<<: *common-build
container_name: superset_app
command: ["/app/docker/docker-bootstrap.sh", "app"]
restart: unless-stopped
ports:
- ${SUPERSET_PORT:-8088}:8088
- 8088:8088
# When in cypress-mode ->
- ${CYPRESS_PORT:-8081}:8081
- 8081:8081
extra_hosts:
- "host.docker.internal:host-gateway"
user: *superset-user
@@ -110,9 +114,10 @@ services:
SUPERSET__SQLALCHEMY_EXAMPLES_URI: "duckdb:////app/data/examples.duckdb"
superset-websocket:
container_name: superset_websocket
build: ./superset-websocket
ports:
- ${WEBSOCKET_PORT:-8080}:8080
- 8080:8080
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
@@ -144,6 +149,7 @@ services:
superset-init:
build:
<<: *common-build
container_name: superset_init
command: ["/app/docker/docker-init.sh"]
env_file:
- path: docker/.env # default
@@ -180,10 +186,9 @@ services:
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
# configuring the dev-server to use the host.docker.internal to connect to the backend
superset: "http://superset:8088"
# Bind to all interfaces so Docker port mapping works
WEBPACK_DEVSERVER_HOST: "0.0.0.0"
ports:
- "127.0.0.1:${NODE_PORT:-9000}:9000" # exposing the dynamic webpack dev server
- "127.0.0.1:9000:9000" # exposing the dynamic webpack dev server
container_name: superset_node
command: ["/app/docker/docker-frontend.sh"]
env_file:
- path: docker/.env # default
@@ -195,6 +200,7 @@ services:
superset-worker:
build:
<<: *common-build
container_name: superset_worker
command: ["/app/docker/docker-bootstrap.sh", "worker"]
env_file:
- path: docker/.env # default
@@ -220,6 +226,7 @@ services:
superset-worker-beat:
build:
<<: *common-build
container_name: superset_worker_beat
command: ["/app/docker/docker-bootstrap.sh", "beat"]
env_file:
- path: docker/.env # default
@@ -237,6 +244,7 @@ services:
superset-tests-worker:
build:
<<: *common-build
container_name: superset_tests_worker
command: ["/app/docker/docker-bootstrap.sh", "worker"]
env_file:
- path: docker/.env # default

View File

@@ -21,15 +21,6 @@ PYTHONUNBUFFERED=1
COMPOSE_PROJECT_NAME=superset
DEV_MODE=true
# Port configuration (override in .env-local for multiple instances)
# NGINX_PORT=80
# SUPERSET_PORT=8088
# NODE_PORT=9000
# WEBSOCKET_PORT=8080
# CYPRESS_PORT=8081
# DATABASE_PORT=5432
# REDIS_PORT=6379
# database configurations (do not modify)
DATABASE_DB=superset
DATABASE_HOST=db

View File

@@ -1,39 +0,0 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# -----------------------------------------------------------------------
# Example .env-local file for running multiple Superset instances
# Copy this file to .env-local and customize for your setup
# -----------------------------------------------------------------------
# Unique project name prevents container/volume conflicts between clones
# Each clone should have a different name (e.g., superset-pr123, superset-feature-x)
COMPOSE_PROJECT_NAME=superset-dev2
# Port offsets for running multiple instances simultaneously
# Instance 1 (default): 80, 8088, 9000, 8080, 8081, 5432, 6379
# Instance 2 example: 81, 8089, 9001, 8082, 8083, 5433, 6380
NGINX_PORT=81
SUPERSET_PORT=8089
NODE_PORT=9001
WEBSOCKET_PORT=8082
CYPRESS_PORT=8083
DATABASE_PORT=5433
REDIS_PORT=6380
# For verbose logging during development:
# SUPERSET_LOG_LEVEL=debug

View File

@@ -77,34 +77,6 @@ To run the container, simply run: `docker compose up`
After waiting several minutes for Superset initialization to finish, you can open a browser and view [`http://localhost:8088`](http://localhost:8088)
to start your journey.
### Running Multiple Instances
If you need to run multiple Superset instances simultaneously (e.g., different branches or clones), use the make targets which automatically find available ports:
```bash
make up
```
This automatically:
- Generates a unique project name from your directory
- Finds available ports (incrementing from defaults if in use)
- Displays the assigned URLs before starting
Available commands (run from repo root):
| Command | Description |
|---------|-------------|
| `make up` | Start services (foreground) |
| `make up-detached` | Start services (background) |
| `make down` | Stop all services |
| `make ps` | Show running containers |
| `make logs` | Follow container logs |
| `make nuke` | Stop, remove volumes & local images |
From a subdirectory, use: `make -C $(git rev-parse --show-toplevel) up`
**Important**: Always use these commands instead of plain `docker compose down`, which won't know the correct project name.
## Developing
While running, the container server will reload on modification of the Superset Python and JavaScript source code.

View File

@@ -139,39 +139,6 @@ docker volume rm superset_db_home
docker-compose up
```
### Running multiple instances
If you need to run multiple Superset clones simultaneously (e.g., testing different branches),
use `make up` instead of `docker compose up`:
```bash
make up
```
This automatically:
- Generates a unique project name from your directory name
- Finds available ports (incrementing from 8088, 9000, etc. if already in use)
- Displays the assigned URLs before starting
Each clone gets isolated containers and volumes, so you can run them side-by-side without conflicts.
Available commands (run from repo root):
| Command | Description |
|---------|-------------|
| `make up` | Start services (foreground) |
| `make up-detached` | Start services (background) |
| `make down` | Stop all services |
| `make ps` | Show running containers |
| `make logs` | Follow container logs |
| `make nuke` | Stop, remove volumes & local images |
From a subdirectory, use: `make -C $(git rev-parse --show-toplevel) up`
:::warning
Always use these commands instead of plain `docker compose down`, which won't know the correct project name for your instance.
:::
## GitHub Codespaces (Cloud Development)
GitHub Codespaces provides a complete, pre-configured development environment in the cloud. This is ideal for:

View File

@@ -138,6 +138,19 @@ The diagram shows:
3. **The host application** implements the APIs and manages extensions
4. **Extensions** integrate seamlessly with the host through well-defined interfaces
### Extension Dependencies
Extensions can depend on any combination of packages based on their needs. For example:
**Frontend-only extension** (e.g., a custom chart type):
- Depends on `@apache-superset/core` for UI components and React APIs
**Full-stack extension** (e.g., a custom SQL editor with new API endpoints):
- Depends on `@apache-superset/core` for frontend components
- Depends on `apache-superset-core` for backend APIs and models
This modular approach allows extension authors to choose exactly what they need while promoting consistency and reusability.
## Dynamic Module Loading
One of the most sophisticated aspects of the extension architecture is how frontend code is dynamically loaded at runtime using Webpack's Module Federation.
@@ -235,7 +248,6 @@ This architecture provides several key benefits:
Now that you understand the architecture, explore:
- **[Dependencies](./dependencies)** - Managing dependencies and understanding API stability
- **[Quick Start](./quick-start)** - Build your first extension
- **[Contribution Types](./contribution-types)** - What kinds of extensions you can build
- **[Development](./development)** - Project structure, APIs, and development workflow

View File

@@ -1,6 +1,6 @@
---
title: Contribution Types
sidebar_position: 5
sidebar_position: 4
---
<!--

View File

@@ -1,166 +0,0 @@
---
title: Dependencies
sidebar_position: 4
---
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
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.
-->
# Dependencies
This guide explains how to manage dependencies in your Superset extensions, including the difference between public APIs and internal code, and best practices for maintaining stable extensions.
## Core Packages vs Internal Code
Extensions run in the same context as Superset during runtime. This means extension developers can technically import any module from the Superset codebase, not just the public APIs. Understanding the distinction between public and internal code is critical for building maintainable extensions.
### Public APIs (Stable)
The core packages follow [semantic versioning](https://semver.org/) and provide stable, documented APIs:
| Package | Language | Description |
|---------|----------|-------------|
| `@apache-superset/core` | JavaScript/TypeScript | Frontend APIs, UI components, hooks, and utilities |
| `apache-superset-core` | Python | Backend APIs, models, DAOs, and utilities |
**Benefits of using core packages:**
- **Semantic versioning**: Breaking changes are communicated through version numbers
- **Documentation**: APIs are documented with clear usage examples
- **Stability commitment**: We strive to maintain backward compatibility
- **Type safety**: Full TypeScript and Python type definitions
### Internal Code (Unstable)
Any code that is not exported through the core packages is considered internal. This includes:
- Direct imports from `superset-frontend/src/` modules
- Direct imports from `superset/` Python modules (outside of `superset_core`)
- Undocumented functions, classes, or utilities
:::warning Use at Your Own Risk
Internal code can change at any time without notice. If you depend on internal modules, your extension may break when Superset is upgraded. There is no guarantee of backward compatibility for internal code.
:::
**Example of internal vs public imports:**
```typescript
// ✅ Public API - stable
import { Button, sqlLab } from '@apache-superset/core';
// ❌ Internal code - may break without notice
import { someInternalFunction } from 'src/explore/components/SomeComponent';
```
```python
# ✅ Public API - stable
from superset_core.api.models import Database
from superset_core.api.daos import DatabaseDAO
# ❌ Internal code - may break without notice
from superset.views.core import SomeInternalClass
```
## API Evolution
The core packages are still evolving. While we follow semantic versioning, the APIs may change as we add new extension points and refine existing ones based on community feedback.
**What this means for extension developers:**
- Check the release notes when upgrading Superset
- Test your extensions against new Superset versions before deploying
- Participate in discussions about API changes to influence the direction
- In some cases, using internal dependencies may be acceptable while the public API is being developed for your use case
### When Internal Dependencies May Be Acceptable
While public APIs are always preferred, there are situations where using internal code may be reasonable:
1. **Missing functionality**: The public API doesn't yet expose what you need
2. **Prototype/experimental extensions**: You're exploring capabilities before committing to a stable implementation
3. **Bridge period**: You need functionality that's planned for the public API but not yet released
In these cases, document your internal dependencies clearly and plan to migrate to public APIs when they become available.
## Core Library Dependencies
An important architectural principle of the Superset extension system is that **we do not provide abstractions on top of core dependencies** like React (frontend) or SQLAlchemy (backend).
### Why We Don't Abstract Core Libraries
Abstracting libraries like React or SQLAlchemy would:
- Create maintenance overhead keeping abstractions in sync with upstream
- Limit access to the full power of these libraries
- Add unnecessary abstraction layers
- Fragment the ecosystem with Superset-specific variants
### Depending on Core Libraries Directly
Extension developers should depend on and use core libraries directly:
**Frontend (examples):**
- [React](https://react.dev/) - UI framework
- [Ant Design](https://ant.design/) - UI component library (prefer Superset components from `@apache-superset/core/ui` when available to preserve visual consistency)
- [Emotion](https://emotion.sh/) - CSS-in-JS styling
- ...
**Backend (examples):**
- [SQLAlchemy](https://www.sqlalchemy.org/) - Database toolkit
- [Flask](https://flask.palletsprojects.com/) - Web framework
- [Flask-AppBuilder](https://flask-appbuilder.readthedocs.io/) - Application framework
- ...
:::info Version Compatibility
When Superset upgrades its core dependencies (e.g., a new major version of Ant Design or SQLAlchemy), extension developers should upgrade their extensions accordingly. This ensures compatibility and access to the latest features and security fixes.
:::
## API Versioning and Changelog
Once the extensions API reaches **v1**, we will maintain a dedicated `CHANGELOG.md` file to track all changes to the public APIs. This will include:
- New APIs and features
- Deprecation notices
- Breaking changes with migration guides
- Bug fixes affecting API behavior
Until then, monitor the Superset release notes and test your extensions with each new release.
## Best Practices
### Do
- **Prefer public APIs**: Always check if functionality exists in `@apache-superset/core` or `apache-superset-core` before using internal code
- **Pin versions**: Specify compatible Superset versions in your extension metadata
- **Test upgrades**: Verify your extension works with new Superset releases before deploying
- **Report missing APIs**: If you need functionality not in the public API, open a GitHub issue to request it
- **Use core libraries directly**: Leverage Ant Design, SQLAlchemy, and other core libraries directly
### Don't
- **Assume stability of internal code**: Internal modules can change or be removed in any release
- **Depend on implementation details**: Even if something works, it may not be supported
- **Skip upgrade testing**: Always test your extension against new Superset versions
- **Expect abstractions**: Use core dependencies directly rather than expecting Superset-specific abstractions
## Next Steps
- **[Architecture](./architecture)** - Understand the extension system design
- **[Development](./development)** - Learn about APIs and development workflow
- **[Quick Start](./quick-start)** - Build your first extension

View File

@@ -1,6 +1,6 @@
---
title: Deployment
sidebar_position: 7
sidebar_position: 6
---
<!--

View File

@@ -1,6 +1,6 @@
---
title: Development
sidebar_position: 6
sidebar_position: 5
---
<!--

View File

@@ -1,7 +1,7 @@
---
title: MCP Integration
hide_title: true
sidebar_position: 8
sidebar_position: 7
version: 1
---

View File

@@ -45,7 +45,6 @@ Extensions can provide:
- **[Quick Start](./quick-start)** - Build your first extension with a complete walkthrough
- **[Architecture](./architecture)** - Design principles and system overview
- **[Dependencies](./dependencies)** - Managing dependencies and understanding API stability
- **[Contribution Types](./contribution-types)** - Available extension points
- **[Development](./development)** - Project structure, APIs, and development workflow
- **[Deployment](./deployment)** - Packaging and deploying extensions

View File

@@ -1,6 +1,6 @@
---
title: Community Extensions
sidebar_position: 10
sidebar_position: 9
---
<!--
@@ -28,17 +28,10 @@ This page serves as a registry of community-created Superset extensions. These e
## Extensions
| Name | Description | Author | Preview |
| ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Extensions API Explorer](https://github.com/michael-s-molina/superset-extensions/tree/main/api_explorer) | A SQL Lab panel that demonstrates the Extensions API by providing an interactive explorer for testing commands like getTabs, getCurrentTab, and getDatabases. Useful for extension developers to understand and experiment with the available APIs. | Michael S. Molina | <a href="/img/extensions/api-explorer.png" target="_blank"><img src="/img/extensions/api-explorer.png" alt="Extensions API Explorer" width="120" /></a> |
| [SQL Query Flow Visualizer](https://github.com/msyavuz/superset-sql-visualizer) | A SQL Lab panel that transforms SQL queries into interactive flow diagrams, helping developers and analysts understand query execution paths and data relationships. | Mehmet Salih Yavuz | <a href="/img/extensions/sql-flow-visualizer.png" target="_blank"><img src="/img/extensions/sql-flow-visualizer.png" alt="SQL Flow Visualizer" width="120" /></a> |
| [SQL Lab Export to Google Sheets](https://github.com/michael-s-molina/superset-extensions/tree/main/sqllab_gsheets) | A Superset extension that allows users to export SQL Lab query results directly to Google Sheets. | Michael S. Molina | <a href="/img/extensions/gsheets-export.png" target="_blank"><img src="/img/extensions/gsheets-export.png" alt="SQL Lab Export to Google Sheets" width="120" /></a> |
| [SQL Lab Export to Parquet](https://github.com/rusackas/superset-extensions/tree/main/sqllab_parquet) | Export SQL Lab query results directly to Apache Parquet format with Snappy compression. | Evan Rusackas | <a href="/img/extensions/parquet-export.png" target="_blank"><img src="/img/extensions/parquet-export.png" alt="SQL Lab Export to Parquet" width="120" /></a> |
| [SQL Lab Query Comparison](https://github.com/michael-s-molina/superset-extensions/tree/main/query_comparison) | A SQL Lab extension that enables side-by-side comparison of query results across different tabs, with GitHub-style diff visualization showing added/removed rows and columns. | Michael S. Molina | <a href="/img/extensions/query-comparison.png" target="_blank"><img src="/img/extensions/query-comparison.png" alt="Query Comparison" width="120" /></a> |
| [SQL Lab Result Stats](https://github.com/michael-s-molina/superset-extensions/tree/main/result_stats) | A SQL Lab extension that automatically computes statistics for query results, providing type-aware analysis including numeric metrics (min, max, mean, median, std dev), string analysis (length, empty counts), and date range information. | Michael S. Molina | <a href="/img/extensions/result-stats.png" target="_blank"><img src="/img/extensions/result-stats.png" alt="Result Stats" width="120" /></a> |
| [SQL Snippets](https://github.com/michael-s-molina/superset-extensions/tree/main/sql_snippets) | A SQL Lab extension that provides reusable SQL code snippets, enabling quick insertion of commonly used code blocks such as license headers, author information, and frequently used SQL patterns. | Michael S. Molina | <a href="/img/extensions/sql-snippets.png" target="_blank"><img src="/img/extensions/sql-snippets.png" alt="SQL Snippets" width="120" /></a> |
| [SQL Lab Query Estimator](https://github.com/michael-s-molina/superset-extensions/tree/main/query_estimator) | A SQL Lab panel that analyzes query execution plans to estimate resource impact, detect performance issues like Cartesian products and high-cost operations, and visualize the query plan tree. | Michael S. Molina | <a href="/img/extensions/query-estimator.png" target="_blank"><img src="/img/extensions/query-estimator.png" alt="Query Estimator" width="120" /></a> |
| Name | Description | Author | Preview |
| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Extensions API Explorer](https://github.com/michael-s-molina/superset-extensions/tree/main/api_explorer) | A SQL Lab panel that demonstrates the Extensions API by providing an interactive explorer for testing commands like getTabs, getCurrentTab, and getDatabases. Useful for extension developers to understand and experiment with the available APIs. | Michael S. Molina | <a href="/img/extensions/api_explorer.png" target="_blank"><img src="/img/extensions/api_explorer.png" alt="Extensions API Explorer" width="120" /></a> |
| [SQL Query Flow Visualizer](https://github.com/msyavuz/superset-sql-visualizer) | A SQL Lab panel that transforms SQL queries into interactive flow diagrams, helping developers and analysts understand query execution paths and data relationships.| Mehmet Salih Yavuz | <a href="/img/extensions/sql_flow_visualizer.png" target="_blank"><img src="/img/extensions/sql_flow_visualizer.png" alt="SQL Flow Visualizer" width="120" /></a> |
## How to Add Your Extension
To add your extension to this registry, submit a pull request to the [Apache Superset repository](https://github.com/apache/superset) with the following changes:

View File

@@ -1,6 +1,6 @@
---
title: Security
sidebar_position: 9
sidebar_position: 8
---
<!--

View File

@@ -36,7 +36,6 @@ module.exports = {
'extensions/overview',
'extensions/quick-start',
'extensions/architecture',
'extensions/dependencies',
'extensions/contribution-types',
{
type: 'category',

View File

@@ -51,20 +51,8 @@ Restart Superset for this configuration change to take effect.
#### Making a Dashboard Public
There are two approaches to making dashboards publicly accessible:
**Option 1: Dataset-based access (simpler)**
1. Set `PUBLIC_ROLE_LIKE = "Public"` in `superset_config.py`
2. Grant the Public role access to the relevant datasets (Menu → Security → List Roles → Public)
3. All published dashboards using those datasets become visible to anonymous users
**Option 2: Dashboard-level access (selective control)**
1. Set `PUBLIC_ROLE_LIKE = "Public"` in `superset_config.py`
2. Add the `'DASHBOARD_RBAC': True` [Feature Flag](https://github.com/apache/superset/blob/master/RESOURCES/FEATURE_FLAGS.md)
3. Edit each dashboard's properties and add the "Public" role
4. Only dashboards with the Public role explicitly assigned are visible to anonymous users
See the [Public role documentation](/docs/security/security#public) for more details.
1. Add the `'DASHBOARD_RBAC': True` [Feature Flag](https://github.com/apache/superset/blob/master/RESOURCES/FEATURE_FLAGS.md) to `superset_config.py`
2. Add the `Public` role to your dashboard as described [here](https://superset.apache.org/docs/using-superset/creating-your-first-dashboard/#manage-access-to-dashboards)
#### Embedding a Public Dashboard

View File

@@ -46,62 +46,12 @@ to all databases by default, both **Alpha** and **Gamma** users need to be given
### Public
The **Public** role is the most restrictive built-in role, designed specifically for anonymous/unauthenticated
users who need to view dashboards. It provides minimal read-only access for:
To allow logged-out users to access some Superset features, you can use the `PUBLIC_ROLE_LIKE` config setting and assign it to another role whose permissions you want passed to this role.
- Viewing dashboards and charts
- Using interactive dashboard filters
- Accessing dashboard and chart permalinks
- Reading embedded dashboards
- Viewing annotations on charts
The Public role explicitly excludes:
- Any write permissions on dashboards, charts, or datasets
- SQL Lab access
- Share functionality
- User profile or admin features
- Menu access to most Superset features
Anonymous users are automatically assigned the Public role when `AUTH_ROLE_PUBLIC` is configured
(a Flask-AppBuilder setting). The `PUBLIC_ROLE_LIKE` setting is **optional** and controls what
permissions are synced to the Public role when you run `superset init`:
```python
# Optional: Sync sensible default permissions to the Public role
PUBLIC_ROLE_LIKE = "Public"
# Alternative: Copy permissions from Gamma for broader access
# PUBLIC_ROLE_LIKE = "Gamma"
```
If you prefer to manually configure the Public role's permissions (or use `DASHBOARD_RBAC` to
grant access at the dashboard level), you do not need to set `PUBLIC_ROLE_LIKE`.
**Important notes:**
- **Data access is still required:** The Public role only grants UI/API permissions. You must
also grant access to specific datasets necessary to view a dashboard. As with other roles,
this can be done in two ways:
- **Without `DASHBOARD_RBAC`:** Dashboards only appear in the list and are accessible if
the user has permission to at least one of their datasets. Grant dataset access by editing
the Public role in the Superset UI (Menu → Security → List Roles → Public) and adding the
relevant data sources. All published dashboards using those datasets become visible.
- **With `DASHBOARD_RBAC` enabled:** Anonymous users will only see dashboards where the
"Public" role has been explicitly added in the dashboard's properties. Dataset permissions
are not required—DASHBOARD_RBAC handles the cascading permissions check. This provides
fine-grained control over which dashboards are publicly visible.
- **Role synchronization:** Built-in role permissions (Admin, Alpha, Gamma, sql_lab, and Public
when `PUBLIC_ROLE_LIKE = "Public"`) are synchronized when you run `superset init`. Any manual
permission edits to these roles may be overwritten during upgrades. To customize the Public
role permissions, you can either:
- Edit the Public role directly and avoid setting `PUBLIC_ROLE_LIKE` (permissions won't be
overwritten by `superset init`)
- Copy the Public role via "Copy Role" in the Superset web UI, save it under a different name
(e.g., "Public_Custom"), customize the permissions, then update **both** configs:
`PUBLIC_ROLE_LIKE = "Public_Custom"` and `AUTH_ROLE_PUBLIC = "Public_Custom"`
For example, by setting `PUBLIC_ROLE_LIKE = "Gamma"` in your `superset_config.py` file, you grant
public role the same set of permissions as for the **Gamma** role. This is useful if one
wants to enable anonymous users to view dashboards. Explicit grant on specific datasets is
still required, meaning that you need to edit the **Public** role and add the public data sources to the role manually.
### Managing Data Source Access for Gamma Roles
@@ -114,46 +64,6 @@ tables in the **Permissions** dropdown. To select the data sources you want to a
You can then confirm with users assigned to the **Gamma** role that they see the
objects (dashboards and slices) associated with the tables you just extended them.
### Dashboard Access Control
Access to dashboards is managed via owners (users that have edit permissions to the dashboard).
Non-owner user access can be managed in two ways. Note that dashboards must be published to be
visible to other users.
#### Dataset-Based Access (Default)
By default, users can view published dashboards if they have access to at least one dataset
used in that dashboard. Grant dataset access by adding the relevant data source permissions
to a role (Menu → Security → List Roles).
This is the simplest approach but provides all-or-nothing access based on dataset permissions—
if a user has access to a dataset, they can see all published dashboards using that dataset.
#### Dashboard-Level Access (DASHBOARD_RBAC)
For fine-grained control over which dashboards specific roles can access, enable the
`DASHBOARD_RBAC` feature flag:
```python
FEATURE_FLAGS = {
"DASHBOARD_RBAC": True,
}
```
With this enabled, you can assign specific roles to each dashboard in its properties. Users
will only see dashboards where their role is explicitly added.
**Important considerations:**
- Dashboard access **bypasses** dataset-level checks—granting a role access to a dashboard
implicitly grants read access to all charts and datasets in that dashboard
- Dashboards without any assigned roles fall back to dataset-based access
- The dashboard must still be published to be visible
This feature is particularly useful for:
- Making specific dashboards public while keeping others private
- Granting access to dashboards without exposing the underlying datasets for other uses
- Creating dashboard-specific access patterns that don't align with dataset ownership
### SQL Execution Security Considerations
Apache Superset includes features designed to provide safeguards when interacting with connected databases, such as the `DISALLOWED_SQL_FUNCTIONS` configuration setting. This aims to prevent the execution of potentially harmful database functions or system variables directly from Superset interfaces like SQL Lab.

View File

@@ -183,12 +183,14 @@ slices and dashboards of your own.
### Manage access to Dashboards
Access to dashboards is managed via owners and permissions. Non-owner access can be controlled
through dataset permissions or dashboard-level roles (using the `DASHBOARD_RBAC` feature flag).
Access to dashboards is managed via owners (users that have edit permissions to the dashboard).
For detailed information on configuring dashboard access, see the
[Dashboard Access Control](/docs/security/security#dashboard-access-control) section in the
Security documentation.
Non-owner users access can be managed in two different ways. The dashboard needs to be published to be visible to other users.
1. Dataset permissions - if you add to the relevant role permissions to datasets it automatically grants implicit access to all dashboards that uses those permitted datasets.
2. Dashboard roles - if you enable [**DASHBOARD_RBAC** feature flag](/docs/configuration/configuring-superset#feature-flags) then you will be able to manage which roles can access the dashboard
- Granting a role access to a dashboard will bypass dataset level checks. Having dashboard access implicitly grants read access to all the featured charts in the dashboard, and thereby also all the associated datasets.
- If no roles are specified for a dashboard, regular **Dataset permissions** will apply.
<img src={useBaseUrl("/img/tutorial/tutorial_dashboard_access.png" )} />

View File

@@ -429,14 +429,6 @@ const config: Config = {
label: 'Stack Overflow',
href: 'https://stackoverflow.com/questions/tagged/apache-superset',
},
{
label: 'Community Calendar',
href: '/community#superset-community-calendar',
},
{
label: 'In the Wild',
href: '/inTheWild',
},
],
},
...dynamicNavbarItems,

View File

@@ -51,11 +51,9 @@
"@storybook/preview-api": "^8.6.11",
"@storybook/theming": "^8.6.11",
"@superset-ui/core": "^0.20.4",
"antd": "^6.1.2",
"caniuse-lite": "^1.0.30001762",
"antd": "^6.1.0",
"caniuse-lite": "^1.0.30001760",
"docusaurus-plugin-less": "^2.0.2",
"js-yaml": "^4.1.1",
"js-yaml-loader": "^1.2.2",
"json-bigint": "^1.0.0",
"less": "^4.5.1",
"less-loader": "^12.3.0",
@@ -66,7 +64,7 @@
"react-svg-pan-zoom": "^3.13.1",
"remark-import-partial": "^0.0.2",
"reselect": "^5.1.1",
"storybook": "^8.6.15",
"storybook": "^8.6.11",
"swagger-ui-react": "^5.31.0",
"tinycolor2": "^1.4.2",
"ts-loader": "^9.5.4",
@@ -76,19 +74,18 @@
"@docusaurus/module-type-aliases": "^3.9.1",
"@docusaurus/tsconfig": "^3.9.2",
"@eslint/js": "^9.39.2",
"@types/js-yaml": "^4.0.9",
"@types/react": "^19.1.8",
"@typescript-eslint/eslint-plugin": "^8.37.0",
"@typescript-eslint/parser": "^8.51.0",
"@typescript-eslint/parser": "^8.49.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.3",
"eslint-plugin-react": "^7.37.5",
"globals": "^17.0.0",
"globals": "^16.5.0",
"prettier": "^3.7.4",
"typescript": "~5.9.3",
"typescript-eslint": "^8.50.1",
"webpack": "^5.104.1"
"typescript-eslint": "^8.49.0",
"webpack": "^5.103.0"
},
"browserslist": {
"production": [

View File

@@ -49,23 +49,9 @@ const BADGE_PATH_PATTERNS = [
// Cache for downloaded badges (persists across files in a single build)
const badgeCache = new Map();
// Track in-flight downloads to prevent duplicate concurrent requests
const inFlightDownloads = new Map();
// Track if we've already ensured the badges directory exists
let badgesDirCreated = false;
// Retry configuration
const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 1000;
/**
* Sleep for a given number of milliseconds
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Generate a stable filename for a badge URL
*/
@@ -88,61 +74,21 @@ function isBadgeUrl(url) {
try {
const parsed = new URL(url);
// Check if it's from a known badge domain
if (BADGE_DOMAINS.some(domain => parsed.hostname.includes(domain))) {
if (BADGE_DOMAINS.some((domain) => parsed.hostname.includes(domain))) {
return true;
}
// Check if it matches a badge path pattern
return BADGE_PATH_PATTERNS.some(pattern => pattern.test(url));
return BADGE_PATH_PATTERNS.some((pattern) => pattern.test(url));
} catch {
return false;
}
}
/**
* Fetch a badge with retry logic
*/
async function fetchWithRetry(url, retries = MAX_RETRIES) {
let lastError;
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fetch(url, {
headers: {
// Some services need a user agent
'User-Agent': 'Mozilla/5.0 (compatible; DocusaurusBuild/1.0)',
Accept: 'image/svg+xml,image/*,*/*',
},
// Follow redirects
redirect: 'follow',
// Add timeout to prevent hanging
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response;
} catch (error) {
lastError = error;
if (attempt < retries) {
const delay = RETRY_DELAY_MS * attempt; // Exponential backoff
console.log(
`[remark-localize-badges] Retry ${attempt}/${retries} for ${url} after ${delay}ms...`,
);
await sleep(delay);
}
}
}
throw lastError;
}
/**
* Download a badge and return the local path
*/
async function downloadBadge(url, staticDir) {
// Check memory cache first
// Check cache first
if (badgeCache.has(url)) {
return badgeCache.get(url);
}
@@ -159,67 +105,58 @@ async function downloadBadge(url, staticDir) {
const localPath = path.join(badgesDir, filename);
const webPath = `/badges/${filename}`;
// Check if already downloaded in a previous build or by another concurrent request
// Check if already downloaded in a previous build
if (fs.existsSync(localPath)) {
badgeCache.set(url, webPath);
return webPath;
}
// Check if there's already an in-flight download for this URL
// This prevents duplicate concurrent downloads of the same badge
if (inFlightDownloads.has(url)) {
return inFlightDownloads.get(url);
}
console.log(`[remark-localize-badges] Downloading: ${url}`);
// Create the download promise and store it
const downloadPromise = (async () => {
// Double-check file existence after acquiring the "lock"
if (fs.existsSync(localPath)) {
badgeCache.set(url, webPath);
return webPath;
try {
const response = await fetch(url, {
headers: {
// Some services need a user agent
'User-Agent': 'Mozilla/5.0 (compatible; DocusaurusBuild/1.0)',
Accept: 'image/svg+xml,image/*,*/*',
},
// Follow redirects
redirect: 'follow',
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
console.log(`[remark-localize-badges] Downloading: ${url}`);
const contentType = response.headers.get('content-type') || '';
const content = await response.text();
try {
const response = await fetchWithRetry(url);
const contentType = response.headers.get('content-type') || '';
const content = await response.text();
// Validate it's actually an SVG or image
if (
!contentType.includes('svg') &&
!contentType.includes('image') &&
!content.trim().startsWith('<svg') &&
!content.trim().startsWith('<?xml')
) {
throw new Error(
`Invalid content type: ${contentType}. Expected SVG image.`,
);
}
// Write the badge to disk
fs.writeFileSync(localPath, content, 'utf8');
console.log(`[remark-localize-badges] Saved: ${filename}`);
badgeCache.set(url, webPath);
return webPath;
} catch (error) {
// Fail the build on badge download failure
// Validate it's actually an SVG or image
if (
!contentType.includes('svg') &&
!contentType.includes('image') &&
!content.trim().startsWith('<svg') &&
!content.trim().startsWith('<?xml')
) {
throw new Error(
`[remark-localize-badges] Failed to download badge: ${url}\n` +
`Error: ${error.message}\n` +
`Build cannot continue with broken badges. Please fix the badge URL or remove it.`,
`Invalid content type: ${contentType}. Expected SVG image.`,
);
} finally {
// Clean up the in-flight tracker
inFlightDownloads.delete(url);
}
})();
inFlightDownloads.set(url, downloadPromise);
return downloadPromise;
// Write the badge to disk
fs.writeFileSync(localPath, content, 'utf8');
console.log(`[remark-localize-badges] Saved: ${filename}`);
badgeCache.set(url, webPath);
return webPath;
} catch (error) {
// Fail the build on badge download failure
throw new Error(
`[remark-localize-badges] Failed to download badge: ${url}\n` +
`Error: ${error.message}\n` +
`Build cannot continue with broken badges. Please fix the badge URL or remove it.`,
);
}
}
/**
@@ -231,14 +168,15 @@ export default function remarkLocalizeBadges(options = {}) {
const docsRoot = path.resolve(currentDir, '..');
const staticDir = options.staticDir || path.join(docsRoot, 'static');
return async function transformer(tree) {
const promises = [];
// Find all image nodes
visit(tree, 'image', node => {
visit(tree, 'image', (node) => {
if (isBadgeUrl(node.url)) {
promises.push(
downloadBadge(node.url, staticDir).then(localPath => {
downloadBadge(node.url, staticDir).then((localPath) => {
node.url = localPath;
}),
);
@@ -246,7 +184,7 @@ export default function remarkLocalizeBadges(options = {}) {
});
// Also handle HTML img tags in raw HTML or JSX
visit(tree, ['html', 'jsx'], node => {
visit(tree, ['html', 'jsx'], (node) => {
if (!node.value) return;
// Find img src attributes pointing to badge URLs
@@ -257,7 +195,7 @@ export default function remarkLocalizeBadges(options = {}) {
const url = match[1];
if (isBadgeUrl(url)) {
promises.push(
downloadBadge(url, staticDir).then(localPath => {
downloadBadge(url, staticDir).then((localPath) => {
node.value = node.value.replace(url, localPath);
}),
);
@@ -266,12 +204,12 @@ export default function remarkLocalizeBadges(options = {}) {
});
// Also handle markdown link images: [![alt](img-url)](link-url)
visit(tree, 'link', node => {
visit(tree, 'link', (node) => {
if (node.children) {
node.children.forEach(child => {
node.children.forEach((child) => {
if (child.type === 'image' && isBadgeUrl(child.url)) {
promises.push(
downloadBadge(child.url, staticDir).then(localPath => {
downloadBadge(child.url, staticDir).then((localPath) => {
child.url = localPath;
}),
);

View File

@@ -72,7 +72,6 @@ const sidebars = {
'extensions/overview',
'extensions/quick-start',
'extensions/architecture',
'extensions/dependencies',
'extensions/contribution-types',
{
type: 'category',

View File

@@ -1,165 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import Layout from '@theme/Layout';
import { Avatar, Card, Col, Collapse, Row, Typography } from 'antd';
import BlurredSection from '../components/BlurredSection';
import SectionHeader from '../components/SectionHeader';
import DataSet from '../../../RESOURCES/INTHEWILD.yaml';
const { Text, Link } = Typography;
interface Organization {
name: string;
url: string;
logo?: string;
contributors?: string[];
}
interface DataSetType {
categories: Record<string, Organization[]>;
}
const typedDataSet = DataSet as DataSetType;
const ContributorAvatars = ({ contributors }: { contributors?: string[] }) => {
if (!contributors?.length) return null;
return (
<Avatar.Group size="small" max={{ count: 3 }}>
{contributors.map((handle) => {
const username = handle.replace('@', '');
return (
<a
key={username}
href={`https://github.com/${username}`}
target="_blank"
rel="noreferrer"
onClick={(e) => e.stopPropagation()}
>
<Avatar
src={`https://github.com/${username}.png?size=40`}
alt={username}
style={{ cursor: 'pointer' }}
>
{username.charAt(0).toUpperCase()}
</Avatar>
</a>
);
})}
</Avatar.Group>
);
};
export default function InTheWild() {
return (
<Layout title="In the Wild" description="Organizations using Apache Superset">
<main>
<BlurredSection>
<SectionHeader
level="h2"
title="In the Wild"
subtitle="See who's using Superset and join our growing community"
/>
<div style={{ textAlign: 'center', marginTop: 10 }}>
<Link
href="https://github.com/apache/superset/edit/master/RESOURCES/INTHEWILD.yaml"
target="_blank"
>
Add your name/org!
</Link>
</div>
</BlurredSection>
<div style={{ maxWidth: 850, margin: '70px auto 60px', padding: '0 20px' }}>
<Collapse
bordered={false}
defaultActiveKey={Object.keys(typedDataSet.categories)}
style={{
background: 'var(--ifm-background-color)',
border: '1px solid var(--ifm-border-color)',
borderRadius: 10,
}}
items={Object.entries(typedDataSet.categories).map(([category, items]) => {
const logoItems = items.filter(({ logo }) => logo?.trim());
const textItems = items.filter(({ logo }) => !logo?.trim());
return {
key: category,
label: (
<Text strong style={{ fontSize: 16, lineHeight: '22px' }}>
{category} ({items.length})
</Text>
),
children: (
<>
{logoItems.length > 0 && (
<Row gutter={[16, 16]} style={{ marginBottom: textItems.length > 0 ? 24 : 0 }}>
{logoItems.map(({ name, url, logo, contributors }) => (
<Col xs={24} sm={12} md={8} key={name}>
<a href={url} target="_blank" rel="noreferrer">
<Card
hoverable
style={{ height: 150, position: 'relative' }}
styles={{ body: { padding: 16, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' } }}
>
<img
src={`/img/logos/${logo}`}
alt={name}
style={{ maxHeight: 80, maxWidth: '100%', objectFit: 'contain' }}
/>
{contributors?.length && (
<div style={{ position: 'absolute', bottom: 8, right: 8 }}>
<ContributorAvatars contributors={contributors} />
</div>
)}
</Card>
</a>
</Col>
))}
</Row>
)}
{textItems.length > 0 && (
<Row gutter={[8, 8]}>
{textItems.map(({ name, url, contributors }) => (
<Col xs={24} sm={12} md={8} key={name}>
<a href={url} target="_blank" rel="noreferrer">
<Card
size="small"
hoverable
styles={{ body: { padding: '8px 12px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 } }}
>
<Text ellipsis style={{ flex: 1 }}>{name}</Text>
<ContributorAvatars contributors={contributors} />
</Card>
</a>
</Col>
))}
</Row>
)}
</>
),
};
})}
/>
</div>
</main>
</Layout>
);
}

View File

@@ -19,43 +19,15 @@
import { useRef, useState, useEffect, JSX } from 'react';
import Layout from '@theme/Layout';
import Link from '@docusaurus/Link';
import { Card, Carousel, Flex } from 'antd';
import { Carousel } from 'antd';
import styled from '@emotion/styled';
import GitHubButton from 'react-github-btn';
import { mq } from '../utils';
import { Databases } from '../resources/data';
import SectionHeader from '../components/SectionHeader';
import BlurredSection from '../components/BlurredSection';
import DataSet from '../../../RESOURCES/INTHEWILD.yaml';
import '../styles/main.less';
interface Organization {
name: string;
url: string;
logo?: string;
}
interface DataSetType {
categories: Record<string, Organization[]>;
}
const typedDataSet = DataSet as DataSetType;
// Extract all organizations with logos for the carousel
const companiesWithLogos = Object.values(typedDataSet.categories)
.flat()
.filter((org) => org.logo?.trim());
// Fisher-Yates shuffle for fair randomization
function shuffleArray<T>(array: T[]): T[] {
const shuffled = [...array];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
}
const features = [
{
image: 'powerful-yet-easy.jpg',
@@ -480,7 +452,6 @@ export default function Home(): JSX.Element {
const slider = useRef(null);
const [slideIndex, setSlideIndex] = useState(0);
const [shuffledCompanies, setShuffledCompanies] = useState(companiesWithLogos);
const onChange = (current, next) => {
setSlideIndex(next);
@@ -508,11 +479,6 @@ export default function Home(): JSX.Element {
}
};
// Shuffle companies on mount for fair rotation
useEffect(() => {
setShuffledCompanies(shuffleArray(companiesWithLogos));
}, []);
// Set up dark <-> light navbar change
useEffect(() => {
changeToDark();
@@ -781,74 +747,6 @@ export default function Home(): JSX.Element {
</span>
</StyledIntegrations>
</BlurredSection>
{/* Only show carousel when we have enough logos (>10) for a good display */}
{companiesWithLogos.length > 10 && (
<BlurredSection>
<div style={{ padding: '0 20px' }}>
<SectionHeader
level="h2"
title="Trusted by teams everywhere"
subtitle="Join thousands of companies using Superset to explore and visualize their data"
/>
<div style={{ maxWidth: 1160, margin: '25px auto 0' }}>
<Carousel
autoplay
autoplaySpeed={2000}
slidesToShow={6}
slidesToScroll={1}
dots={false}
responsive={[
{ breakpoint: 1024, settings: { slidesToShow: 4 } },
{ breakpoint: 768, settings: { slidesToShow: 3 } },
{ breakpoint: 480, settings: { slidesToShow: 2 } },
]}
>
{shuffledCompanies.map(({ name, url, logo }) => (
<div key={name}>
<a
href={url}
target="_blank"
rel="noreferrer"
aria-label={`Visit ${name}`}
>
<Card
style={{ margin: '0 8px' }}
styles={{
body: {
height: 80,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: 16,
},
}}
>
<img
src={`/img/logos/${logo}`}
alt={name}
title={name}
style={{ maxHeight: 48, maxWidth: '100%', objectFit: 'contain' }}
/>
</Card>
</a>
</div>
))}
</Carousel>
</div>
<Flex justify="center" style={{ marginTop: 30, fontSize: 17 }}>
<Link to="/inTheWild">See all companies</Link>
<span style={{ margin: '0 8px' }}>·</span>
<a
href="https://github.com/apache/superset/edit/master/RESOURCES/INTHEWILD.yaml"
target="_blank"
rel="noreferrer"
>
Add yours to the list!
</a>
</Flex>
</div>
</BlurredSection>
)}
</StyledMain>
</Layout>
);

View File

@@ -19,17 +19,6 @@
import { useEffect } from 'react';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
// File extensions to track as downloads
const DOWNLOAD_EXTENSIONS = [
'pdf', 'zip', 'tar', 'gz', 'tgz', 'bz2',
'exe', 'dmg', 'pkg', 'deb', 'rpm',
'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
'csv', 'json', 'yaml', 'yml',
];
// Scroll depth milestones to track
const SCROLL_MILESTONES = [25, 50, 75, 100];
export default function Root({ children }) {
const { siteConfig } = useDocusaurusContext();
const { customFields } = siteConfig;
@@ -38,9 +27,10 @@ export default function Root({ children }) {
const { matomoUrl, matomoSiteId } = customFields;
if (typeof window !== 'undefined') {
const devMode = ['localhost', '127.0.0.1', '::1', '0.0.0.0'].includes(window.location.hostname);
// Making testing easier, logging debug junk if we're in development
const devMode = window.location.hostname === 'localhost' ? true : false;
// Initialize the _paq array
// Initialize the _paq array first
window._paq = window._paq || [];
// Configure the tracker before loading matomo.js
@@ -49,8 +39,7 @@ export default function Root({ children }) {
window._paq.push(['setTrackerUrl', `${matomoUrl}/matomo.php`]);
window._paq.push(['setSiteId', matomoSiteId]);
// Track downloads with custom extensions
window._paq.push(['setDownloadExtensions', DOWNLOAD_EXTENSIONS.join('|')]);
// Initial page view is handled by handleRouteChange
// Now load the matomo.js script
const script = document.createElement('script');
@@ -58,168 +47,19 @@ export default function Root({ children }) {
script.src = `${matomoUrl}/matomo.js`;
document.head.appendChild(script);
// Helper to track events
const trackEvent = (category, action, name, value) => {
if (devMode) {
console.log('Matomo trackEvent:', { category, action, name, value });
}
window._paq.push(['trackEvent', category, action, name, value]);
};
// Helper to track site search
const trackSiteSearch = (keyword, category, resultsCount) => {
if (devMode) {
console.log('Matomo trackSiteSearch:', { keyword, category, resultsCount });
}
window._paq.push(['trackSiteSearch', keyword, category, resultsCount]);
};
// Track external link clicks using domain as category (vendor-agnostic)
const handleLinkClick = (event) => {
const link = event.target.closest('a');
if (!link) return;
const href = link.getAttribute('href');
if (!href) return;
try {
const url = new URL(href, window.location.origin);
// Skip internal links
if (url.hostname === window.location.hostname) return;
// Use hostname as category for vendor-agnostic tracking
trackEvent('Outbound Link', url.hostname, href);
} catch {
// Invalid URL, skip tracking
}
};
// Track Algolia search queries
const setupAlgoliaTracking = () => {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const searchInput = node.querySelector?.('.DocSearch-Input') ||
(node.classList?.contains('DocSearch-Input') ? node : null);
if (searchInput) {
let debounceTimer;
searchInput.addEventListener('input', (e) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
const query = e.target.value.trim();
if (query.length >= 3) {
const results = document.querySelectorAll('.DocSearch-Hit');
trackSiteSearch(query, 'Documentation', results.length);
}
}, 1000);
});
}
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
return observer;
};
// Track video plays
const handleVideoPlay = (event) => {
if (event.target.tagName === 'VIDEO') {
const videoSrc = event.target.currentSrc || event.target.src || 'unknown';
trackEvent('Video', 'Play', videoSrc);
}
};
// Track CTA button clicks
const handleCTAClick = (event) => {
const button = event.target.closest('.get-started-button, .default-button-theme');
if (button) {
const buttonText = button.textContent?.trim() || 'Unknown';
const href = button.getAttribute('href') || '';
trackEvent('CTA', 'Click', `${buttonText} - ${href}`);
}
};
// Track scroll depth
let scrollMilestonesReached = new Set();
const handleScroll = () => {
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
if (docHeight <= 0) return;
const scrollPercent = Math.round((scrollTop / docHeight) * 100);
SCROLL_MILESTONES.forEach(milestone => {
if (scrollPercent >= milestone && !scrollMilestonesReached.has(milestone)) {
scrollMilestonesReached.add(milestone);
trackEvent('Scroll Depth', `${milestone}%`, window.location.pathname);
}
});
};
// Reset scroll tracking on route change
const resetScrollTracking = () => {
scrollMilestonesReached = new Set();
};
// Track 404 pages
const track404 = () => {
const is404 = document.querySelector('.theme-doc-404') ||
document.title.toLowerCase().includes('not found') ||
document.querySelector('h1')?.textContent?.toLowerCase().includes('not found');
if (is404) {
trackEvent('Error', '404', window.location.pathname);
if (devMode) {
console.log('Matomo: 404 page detected', window.location.pathname);
}
}
};
// Track copy-to-clipboard events on code blocks
const handleCopy = (event) => {
const codeBlock = event.target.closest('pre, code, .prism-code');
if (codeBlock) {
const codeText = window.getSelection()?.toString() || '';
const codeSnippet = codeText.substring(0, 100) + (codeText.length > 100 ? '...' : '');
trackEvent('Code', 'Copy', `${window.location.pathname}: ${codeSnippet}`);
}
};
// Track color mode preference (as event, no admin config needed)
const trackColorMode = () => {
const colorMode = document.documentElement.getAttribute('data-theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
trackEvent('User Preference', 'Color Mode', colorMode);
};
// Track docs version from URL (as event, no admin config needed)
const trackDocsVersion = () => {
const pathMatch = window.location.pathname.match(/\/docs\/([\d.]+)\//);
const version = pathMatch ? pathMatch[1] : 'latest';
trackEvent('User Preference', 'Docs Version', version);
};
// Handle route changes for SPA
const handleRouteChange = () => {
if (devMode) {
console.log('Route changed to:', window.location.pathname);
}
// Reset scroll tracking for new page
resetScrollTracking();
// Short timeout to ensure the page has fully rendered
setTimeout(() => {
// Get the current page title from the document
const currentTitle = document.title;
const currentPath = window.location.pathname;
// Set custom dimensions before tracking page view
trackColorMode();
trackDocsVersion();
// For testing: impersonate real domain - ONLY FOR DEVELOPMENT
if (devMode) {
console.log('Tracking page view:', currentPath, currentTitle);
window._paq.push(['setDomains', ['superset.apache.org']]);
@@ -234,13 +74,10 @@ export default function Root({ children }) {
window._paq.push(['setReferrerUrl', window.location.href]);
window._paq.push(['setDocumentTitle', currentTitle]);
window._paq.push(['trackPageView']);
// Check for 404 after page renders
setTimeout(track404, 500);
}, 100);
}, 100); // Increased delay to ensure page has fully rendered
};
// Set up Docusaurus route listeners
// Try all possible Docusaurus events - they've changed between versions
const possibleEvents = [
'docusaurus.routeDidUpdate',
'docusaurusRouteDidUpdate',
@@ -248,22 +85,21 @@ export default function Root({ children }) {
];
if (devMode) {
console.log('Setting up Matomo tracking with enhanced features');
console.log('Setting up Docusaurus route listeners');
}
// Store handler references for proper cleanup
const routeHandlers = possibleEvents.map(eventName => {
const handler = () => {
possibleEvents.forEach(eventName => {
document.addEventListener(eventName, () => {
if (devMode) {
console.log(`Docusaurus route update detected via ${eventName}`);
}
handleRouteChange();
};
document.addEventListener(eventName, handler);
return { eventName, handler };
});
});
// Manual history tracking as fallback
// Also set up manual history tracking as fallback
if (devMode) {
console.log('Setting up manual history tracking as fallback');
}
const originalPushState = window.history.pushState;
window.history.pushState = function () {
originalPushState.apply(this, arguments);
@@ -272,53 +108,19 @@ export default function Root({ children }) {
window.addEventListener('popstate', handleRouteChange);
// Set up event listeners
document.addEventListener('click', handleLinkClick);
document.addEventListener('click', handleCTAClick);
document.addEventListener('play', handleVideoPlay, true);
document.addEventListener('copy', handleCopy);
window.addEventListener('scroll', handleScroll, { passive: true });
// Watch for color mode changes
const colorModeObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'data-theme') {
trackEvent('User Preference', 'Color Mode Change',
document.documentElement.getAttribute('data-theme'));
}
});
});
colorModeObserver.observe(document.documentElement, { attributes: true });
// Set up Algolia tracking
const algoliaObserver = setupAlgoliaTracking();
// Initial page tracking
handleRouteChange();
// Cleanup
return () => {
routeHandlers.forEach(({ eventName, handler }) => {
document.removeEventListener(eventName, handler);
// Cleanup listeners
possibleEvents.forEach(eventName => {
document.removeEventListener(eventName, handleRouteChange);
});
if (originalPushState) {
window.history.pushState = originalPushState;
window.removeEventListener('popstate', handleRouteChange);
}
document.removeEventListener('click', handleLinkClick);
document.removeEventListener('click', handleCTAClick);
document.removeEventListener('play', handleVideoPlay, true);
document.removeEventListener('copy', handleCopy);
window.removeEventListener('scroll', handleScroll);
if (algoliaObserver) {
algoliaObserver.disconnect();
}
if (colorModeObserver) {
colorModeObserver.disconnect();
}
};
}
}, []);

View File

@@ -25,13 +25,6 @@ export default function webpackExtendPlugin(): Plugin<void> {
name: 'custom-webpack-plugin',
configureWebpack(config) {
const isDev = process.env.NODE_ENV === 'development';
// Add YAML loader rule directly to existing rules
config.module?.rules?.push({
test: /\.ya?ml$/,
use: 'js-yaml-loader',
});
return {
devtool: isDev ? 'eval-source-map' : config.devtool,
...(isDev && {

View File

Before

Width:  |  Height:  |  Size: 476 KiB

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 358 KiB

View File

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

View File

@@ -1,19 +0,0 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 494.72 148.76"><defs><style>.cls-1{fill:#2fc096;}</style></defs><title>preset-logo</title><path class="cls-1" d="M91,51.77H43.11L0,145H27.81L59.3,77.14H91a12.69,12.69,0,0,0,0-25.37ZM91,0H61.28L49.51,25.37H91a39.08,39.08,0,0,1,0,78.16H74.8L63,128.84a12.1,12.1,0,0,0,1.21.06H91A64.45,64.45,0,0,0,91,0Z"/><path d="M205,38.46c-14.41,0-24.47,10.31-24.47,25.07V146a2.66,2.66,0,0,0,2.75,2.75h5.62a2.66,2.66,0,0,0,2.75-2.75V114.25a20.32,20.32,0,0,0,14.72,5.65c13.17,0,23.1-10.78,23.1-25.08V63.53C229.5,48.77,219.44,38.46,205,38.46ZM191.68,63.53c0-8.66,5.24-14.26,13.35-14.26s13.35,5.6,13.35,14.26V94.82c0,8.66-5.24,14.26-13.35,14.26s-13.35-5.6-13.35-14.26Z"/><path d="M272.15,39.52h-6.68c-14.53,0-25.08,10.35-25.08,24.62v51.49a2.66,2.66,0,0,0,2.75,2.75h5.62a2.67,2.67,0,0,0,2.76-2.75V64.14c0-8.51,5.34-13.8,14-13.8h6.68a2.67,2.67,0,0,0,2.76-2.76V42.27A2.66,2.66,0,0,0,272.15,39.52Z"/><path d="M304.2,86.64c14.17,0,24.47-9.85,24.47-23.41,0-14.5-10.07-24.63-24.47-24.63-14.64,0-24.47,10.14-24.47,25.24V94.67c0,15,9.83,25.08,24.47,25.08,14.06,0,23.84-10.06,24.32-25.08a2.66,2.66,0,0,0-2.76-2.75h-5.62a2.57,2.57,0,0,0-2.75,2.71c-.28,8.69-5.46,14.3-13.19,14.3-8.11,0-13.35-5.6-13.35-14.26v-8Zm-13.35-22.8c0-9,5-14.41,13.35-14.41,8.1,0,13.34,5.41,13.34,13.8,0,7.64-5.24,12.59-13.34,12.59H290.85Z"/><path d="M363.68,73.69c-8.11-3.42-15.12-6.37-15.12-13.5,0-6.53,5-10.92,12.44-10.92S373.13,53.94,373.58,62a2.59,2.59,0,0,0,2.76,2.75H382A2.66,2.66,0,0,0,384.7,62c-.67-14.94-9.47-23.5-24.16-23.5-13.17,0-23.11,9.34-23.11,21.73,0,14.52,11.48,19.4,21.6,23.7,8.38,3.56,15.62,6.64,15.62,14.27,0,6.94-4.54,10.92-12.44,10.92-9.28,0-14-4.68-14.41-14.26a2.58,2.58,0,0,0-2.75-2.75h-5.62a2.66,2.66,0,0,0-2.75,2.75v.05c.65,15.44,10.43,25,25.53,25,13.43,0,23.56-9.35,23.56-21.74C385.77,83,374,78.05,363.68,73.69Z"/><path d="M418.55,86.64c14.18,0,24.47-9.85,24.47-23.41C443,48.73,433,38.6,418.55,38.6c-14.64,0-24.47,10.14-24.47,25.24V94.67c0,15,9.83,25.08,24.47,25.08,14.07,0,23.84-10.06,24.32-25.08a2.66,2.66,0,0,0-2.75-2.75H434.5a2.57,2.57,0,0,0-2.75,2.71c-.29,8.69-5.47,14.3-13.2,14.3-8.11,0-13.35-5.6-13.35-14.26v-8ZM405.2,63.84c0-9,5-14.41,13.35-14.41,8.11,0,13.35,5.41,13.35,13.8,0,7.64-5.24,12.59-13.35,12.59H405.2Z"/><path d="M478.14,108.05h-2c-7.65,0-11.53-5.36-11.53-15.93V50.83h11.68a2.67,2.67,0,0,0,2.75-2.76V42.91a2.66,2.66,0,0,0-2.75-2.75H464.64V21.65a2.67,2.67,0,0,0-2.75-2.76h-5.62a2.67,2.67,0,0,0-2.75,2.76V92.12c0,16.75,8.46,26.75,22.65,26.75h2a2.65,2.65,0,0,0,2.75-2.75V110.8A2.58,2.58,0,0,0,478.14,108.05Z"/><path d="M484,27h-1.91V25.85h5.18V27h-1.91v5.17H484Z"/><path d="M493.41,29.77c0-1.07,0-2.27,0-3h0c-.3,1.28-.93,3.37-1.53,5.34h-1.16c-.46-1.72-1.11-4.1-1.38-5.36h0c.06.74.08,2,.08,3.11v2.25h-1.24V25.85h2c.49,1.64,1,3.7,1.23,4.63h0c.15-.82.84-3,1.37-4.63h2v6.28h-1.31Z"/></svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

64
docs/static/llms.txt vendored
View File

@@ -1,64 +0,0 @@
# Apache Superset
> Apache Superset is a modern, enterprise-ready business intelligence web application. It provides a no-code interface for building charts, a powerful SQL editor, support for nearly any SQL database, and beautiful visualizations. Superset is open source under the Apache 2.0 license.
Superset is designed for data exploration and visualization at scale. It features a lightweight semantic layer, configurable caching, extensible security with RBAC, a REST API for programmatic access, and a cloud-native architecture.
## Getting Started
- [Introduction](https://superset.apache.org/docs/intro): Overview of Superset features, supported databases, and resources
- [Quickstart](https://superset.apache.org/docs/quickstart): Get Superset running quickly with Docker Compose
## Installation
- [Architecture](https://superset.apache.org/docs/installation/architecture): Production deployment architecture and components
- [Docker Compose](https://superset.apache.org/docs/installation/docker-compose): Install Superset using Docker Compose
- [Docker Builds](https://superset.apache.org/docs/installation/docker-builds): Building and customizing Docker images
- [Kubernetes](https://superset.apache.org/docs/installation/kubernetes): Deploy Superset on Kubernetes with Helm
- [PyPI](https://superset.apache.org/docs/installation/pypi): Install from PyPI using pip
- [Upgrading Superset](https://superset.apache.org/docs/installation/upgrading-superset): Upgrade between Superset versions
## Configuration
- [Configuring Superset](https://superset.apache.org/docs/configuration/configuring-superset): Main configuration options and superset_config.py
- [Databases](https://superset.apache.org/docs/configuration/databases): Connect to databases with SQLAlchemy connection strings
- [Caching](https://superset.apache.org/docs/configuration/cache): Configure caching with Redis or other backends
- [Async Queries & Celery](https://superset.apache.org/docs/configuration/async-queries-celery): Set up async query execution with Celery
- [Alerts & Reports](https://superset.apache.org/docs/configuration/alerts-reports): Configure email/Slack alerts and scheduled reports
- [SQL Templating](https://superset.apache.org/docs/configuration/sql-templating): Use Jinja templates in SQL queries
- [Theming](https://superset.apache.org/docs/configuration/theming): Customize Superset's appearance
- [Networking Settings](https://superset.apache.org/docs/configuration/networking-settings): CORS, proxy, and network configuration
- [Timezones](https://superset.apache.org/docs/configuration/timezones): Timezone handling configuration
- [Event Logging](https://superset.apache.org/docs/configuration/event-logging): Configure event and analytics logging
- [Importing/Exporting Datasources](https://superset.apache.org/docs/configuration/importing-exporting-datasources): Import and export datasource definitions
## Using Superset
- [Creating Your First Dashboard](https://superset.apache.org/docs/using-superset/creating-your-first-dashboard): Step-by-step tutorial for building dashboards
- [Exploring Data](https://superset.apache.org/docs/using-superset/exploring-data): Guide to data exploration features
- [Issue Codes](https://superset.apache.org/docs/using-superset/issue-codes): Reference for Superset error codes
## Security
- [Security Overview](https://superset.apache.org/docs/security/security): RBAC, row-level security, and authentication options
- [Securing Superset](https://superset.apache.org/docs/security/securing_superset): Production security hardening guide
- [CVEs](https://superset.apache.org/docs/security/cves): Security vulnerabilities and advisories
## Contributing
- [Contributing Guide](https://superset.apache.org/docs/contributing/contributing): How to contribute to Apache Superset
- [Development Environment](https://superset.apache.org/docs/contributing/development): Set up a local development environment
- [Guidelines](https://superset.apache.org/docs/contributing/guidelines): Code style, testing, and PR guidelines
- [How-Tos](https://superset.apache.org/docs/contributing/howtos): Common development tasks and recipes
- [Resources](https://superset.apache.org/docs/contributing/resources): Additional contributor resources
## Reference
- [FAQ](https://superset.apache.org/docs/faq): Frequently asked questions
- [REST API](https://superset.apache.org/docs/api): Superset REST API documentation
## Optional
- [Country Map Tools](https://superset.apache.org/docs/configuration/country-map-tools): Custom country map visualizations
- [Map Tiles](https://superset.apache.org/docs/configuration/map-tiles): Configure map tile providers for deck.gl charts
- [Miscellaneous](https://superset.apache.org/docs/contributing/misc): Additional contributor information

View File

@@ -2762,19 +2762,19 @@
"@rc-component/util" "^1.2.1"
clsx "^2.1.1"
"@rc-component/form@~1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@rc-component/form/-/form-1.6.0.tgz#d2f9f6be1063886b9a0ccc5168a0411da6e0da15"
integrity sha512-A7vrN8kExtw4sW06mrsgCb1rowhvBFFvQU6Bk/NL0Fj6Wet/5GF0QnGCxBu/sG3JI9FEhsJWES0D44BW2d0hzg==
"@rc-component/form@~1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@rc-component/form/-/form-1.4.0.tgz#bee504c182bbb768b5fb68809e82b69deef9aec0"
integrity sha512-C8MN/2wIaW9hSrCCtJmcgCkWTQNIspN7ARXLFA4F8PGr8Qxk39U5pS3kRK51/bUJNhb/fEtdFnaViLlISGKI2A==
dependencies:
"@rc-component/async-validator" "^5.0.3"
"@rc-component/util" "^1.5.0"
"@rc-component/util" "^1.3.0"
clsx "^2.1.1"
"@rc-component/image@~1.5.3":
version "1.5.3"
resolved "https://registry.yarnpkg.com/@rc-component/image/-/image-1.5.3.tgz#ea163e5b55303d548e3b2946e99bdcd9e7586299"
integrity sha512-/NR7QW9uCN8Ugar+xsHZOPvzPySfEhcW2/vLcr7VPRM+THZMrllMRv7LAUgW7ikR+Z67Ab67cgPp5K5YftpJsQ==
"@rc-component/image@~1.5.2":
version "1.5.2"
resolved "https://registry.yarnpkg.com/@rc-component/image/-/image-1.5.2.tgz#46cd467466f8b5c9a682bbc96a04f15ad3688af6"
integrity sha512-SIbYLy0IrXqyhccpKktQEvpbBti/KwgG8V/E8GJa8ycwOQmuZaCP7b/C+eQlivn4KDWpfKfoOrLKHXmVlljDgg==
dependencies:
"@rc-component/motion" "^1.0.0"
"@rc-component/portal" "^2.0.0"
@@ -2870,10 +2870,10 @@
"@rc-component/util" "^1.3.0"
clsx "^2.1.1"
"@rc-component/picker@~1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@rc-component/picker/-/picker-1.9.0.tgz#5ecb5595d2fcf0b4ec4edc9202628f42a314c4b0"
integrity sha512-OLisdk8AWVCG9goBU1dWzuH5QlBQk8jktmQ6p0/IyBFwdKGwyIZOSjnBYo8hooHiTdl0lU+wGf/OfMtVBw02KQ==
"@rc-component/picker@~1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@rc-component/picker/-/picker-1.8.0.tgz#4fe2965acd804995123d7bd81d184c2c81f92f8b"
integrity sha512-ek4efrIy+peC8WFJg6Lg7c+WNkykr+wUGQGBNoKmlF0K752aIJuaPcBj6p8CceT9vSJ9gOeeclQCBQIFWVDk1A==
dependencies:
"@rc-component/overflow" "^1.0.0"
"@rc-component/resize-observer" "^1.0.0"
@@ -2919,20 +2919,20 @@
dependencies:
"@rc-component/util" "^1.2.0"
"@rc-component/segmented@~1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@rc-component/segmented/-/segmented-1.3.0.tgz#46f5e3f34630a246e02fe20fd501ed448eb64c17"
integrity sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==
"@rc-component/segmented@~1.2.3":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@rc-component/segmented/-/segmented-1.2.3.tgz#3a1b8d5daa2ecba6876062abb01424bbd512ef79"
integrity sha512-L7G4S6zUpqHclOXK0wKKN2/VyqHa9tfDNxkoFjWOTPtQ0ROFaBwZhbf1+9sdZfIFkxJkpcShAmDOMEIBaFFqkw==
dependencies:
"@babel/runtime" "^7.11.1"
"@rc-component/motion" "^1.1.4"
"@rc-component/util" "^1.3.0"
clsx "^2.1.1"
"@rc-component/select@~1.3.0", "@rc-component/select@~1.3.6":
version "1.3.6"
resolved "https://registry.yarnpkg.com/@rc-component/select/-/select-1.3.6.tgz#3272fb12382d14e8d51f20de26b3cf39feb163bc"
integrity sha512-CzbJ9TwmWcF5asvTMZ9BMiTE9CkkrigeOGRPpzCNmeZP7KBwwmYrmOIiKh9tMG7d6DyGAEAQ75LBxzPx+pGTHA==
"@rc-component/select@~1.3.0", "@rc-component/select@~1.3.2":
version "1.3.4"
resolved "https://registry.yarnpkg.com/@rc-component/select/-/select-1.3.4.tgz#e1c0756290ae4ed3d6823b36de536222752b1193"
integrity sha512-NKhzahL/lXk3aKtmeH5W/jIqaPKcx9QiFXOvJxKe8eiuusIcSCW+XvJdjY3nRvCpTZCZDp7e1RaCU95gohx6Ow==
dependencies:
"@rc-component/overflow" "^1.0.0"
"@rc-component/trigger" "^3.0.0"
@@ -3036,10 +3036,10 @@
"@rc-component/virtual-list" "^1.0.1"
clsx "^2.1.1"
"@rc-component/trigger@^3.0.0", "@rc-component/trigger@^3.6.15", "@rc-component/trigger@^3.7.1", "@rc-component/trigger@^3.7.2":
version "3.7.2"
resolved "https://registry.yarnpkg.com/@rc-component/trigger/-/trigger-3.7.2.tgz#9ae9192c024c35432106904785359f8a46fa1412"
integrity sha512-25x+D2k9SAkaK/MNMNmv2Nlv8FH1D9RtmjoMoLEw1Cid+sMV4pAAT5k49ku59UeXaOA1qwLUVrBUMq4A6gUSsQ==
"@rc-component/trigger@^3.0.0", "@rc-component/trigger@^3.6.15", "@rc-component/trigger@^3.7.1":
version "3.7.1"
resolved "https://registry.yarnpkg.com/@rc-component/trigger/-/trigger-3.7.1.tgz#3b84eb77a6ea99f240b5fa4c06a2dea34b65d3d5"
integrity sha512-+YNP8FywxKJpdqzlAp6TN8UbSK6YsQtIs3kI13mHfm87qi3qUd5Q9AGW8Unfv76kXFUSu7U7D0FygRsGH+6MiA==
dependencies:
"@rc-component/motion" "^1.1.4"
"@rc-component/portal" "^2.0.0"
@@ -3055,7 +3055,7 @@
"@rc-component/util" "^1.3.0"
clsx "^2.1.1"
"@rc-component/util@^1.0.1", "@rc-component/util@^1.1.0", "@rc-component/util@^1.2.0", "@rc-component/util@^1.2.1", "@rc-component/util@^1.3.0", "@rc-component/util@^1.4.0", "@rc-component/util@^1.5.0", "@rc-component/util@^1.6.0":
"@rc-component/util@^1.0.1", "@rc-component/util@^1.1.0", "@rc-component/util@^1.2.0", "@rc-component/util@^1.2.1", "@rc-component/util@^1.3.0", "@rc-component/util@^1.4.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@rc-component/util/-/util-1.6.0.tgz#4f700da5417eb5fd5f9491f08edcba6d075d9454"
integrity sha512-YbjuIVAm8InCnXVoA4n6G+uh31yESTxQ6fSY2frZ2/oMSvktoB+bumFUfNN7RKh7YeOkZgOvN2suGtEDhJSX0A==
@@ -3170,12 +3170,12 @@
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-8.6.14.tgz#ba2be7b0644655d17db143b2be5f45199e617af4"
integrity sha512-RrJ95u3HuIE4Nk8VmZP0tc/u0vYoE2v9fYlMw6K2GUSExzKDITs3voy6WMIY7Q3qbQun8XUXVlmqkuFzTEy/pA==
"@storybook/core@8.6.15", "@storybook/core@^8.6.11":
version "8.6.15"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-8.6.15.tgz#ba3aa59b02981136fa671d545b64d380c1188c8b"
integrity sha512-VFpKcphNurJpSC4fpUfKL3GTXVoL53oytghGR30QIw5jKWwaT50HVbTyb41BLOUuZjmMhUQA8weiQEew6RX0gw==
"@storybook/core@8.6.14", "@storybook/core@^8.6.11":
version "8.6.14"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-8.6.14.tgz#335b067709fd649512b6553b31ad48c8c56f7ed9"
integrity sha512-1P/w4FSNRqP8j3JQBOi3yGt8PVOgSRbP66Ok520T78eJBeqx9ukCfl912PQZ7SPbW3TIunBwLXMZOjZwBB/JmA==
dependencies:
"@storybook/theming" "8.6.15"
"@storybook/theming" "8.6.14"
better-opn "^3.0.2"
browser-assert "^1.2.1"
esbuild "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0"
@@ -3221,12 +3221,7 @@
resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-8.6.14.tgz#02fc8aeab701040744d93b6ef46b9e5727123370"
integrity sha512-0hixr3dOy3f3M+HBofp3jtMQMS+sqzjKNgl7Arfuj3fvjmyXOks/yGjDImySR4imPtEllvPZfhiQNlejheaInw==
"@storybook/theming@8.6.15":
version "8.6.15"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-8.6.15.tgz#781e6b36f113a10e76379956b9451276e8d5bda2"
integrity sha512-dAbL0XOekyT6XsF49R6Etj3WxQ/LpdJDIswUUeHgVJ6/yd2opZOGbPxnwA3zlmAh1c0tvpPyhSDXxSG79u8e4Q==
"@storybook/theming@^8.6.11":
"@storybook/theming@8.6.14", "@storybook/theming@^8.6.11":
version "8.6.14"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-8.6.14.tgz#78c6dc878f705de70c67f2b2d08b8313b985d81a"
integrity sha512-r4y+LsiB37V5hzpQo+BM10PaCsp7YlZ0YcZzQP1OCkPlYXmUAFy2VvDKaFRpD8IeNPKug2u4iFm/laDEbs03dg==
@@ -4216,11 +4211,6 @@
dependencies:
"@types/istanbul-lib-report" "*"
"@types/js-yaml@^4.0.9":
version "4.0.9"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2"
integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==
"@types/json-bigint@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@types/json-bigint/-/json-bigint-1.0.4.tgz#250d29e593375499d8ba6efaab22d094c3199ef3"
@@ -4440,161 +4430,100 @@
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@8.50.1", "@typescript-eslint/eslint-plugin@^8.37.0":
version "8.50.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz#b56e422fb82eb40fae04905f1444aef0298b634b"
integrity sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==
"@typescript-eslint/eslint-plugin@8.49.0", "@typescript-eslint/eslint-plugin@^8.37.0":
version "8.49.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.49.0.tgz#8ed8736b8415a9193989220eadb6031dbcd2260a"
integrity sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==
dependencies:
"@eslint-community/regexpp" "^4.10.0"
"@typescript-eslint/scope-manager" "8.50.1"
"@typescript-eslint/type-utils" "8.50.1"
"@typescript-eslint/utils" "8.50.1"
"@typescript-eslint/visitor-keys" "8.50.1"
"@typescript-eslint/scope-manager" "8.49.0"
"@typescript-eslint/type-utils" "8.49.0"
"@typescript-eslint/utils" "8.49.0"
"@typescript-eslint/visitor-keys" "8.49.0"
ignore "^7.0.0"
natural-compare "^1.4.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/parser@8.50.1":
version "8.50.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.50.1.tgz#9772760c0c4090ba3e8b43c796128ff88aff345c"
integrity sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==
"@typescript-eslint/parser@8.49.0", "@typescript-eslint/parser@^8.49.0":
version "8.49.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.49.0.tgz#0ede412d59e99239b770f0f08c76c42fba717fa2"
integrity sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==
dependencies:
"@typescript-eslint/scope-manager" "8.50.1"
"@typescript-eslint/types" "8.50.1"
"@typescript-eslint/typescript-estree" "8.50.1"
"@typescript-eslint/visitor-keys" "8.50.1"
"@typescript-eslint/scope-manager" "8.49.0"
"@typescript-eslint/types" "8.49.0"
"@typescript-eslint/typescript-estree" "8.49.0"
"@typescript-eslint/visitor-keys" "8.49.0"
debug "^4.3.4"
"@typescript-eslint/parser@^8.51.0":
version "8.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.51.0.tgz#584fb8be3a867cbf980917aabed5f7528f615d6b"
integrity sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==
"@typescript-eslint/project-service@8.49.0":
version "8.49.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.49.0.tgz#ce220525c88cb2d23792b391c07e14cb9697651a"
integrity sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==
dependencies:
"@typescript-eslint/scope-manager" "8.51.0"
"@typescript-eslint/types" "8.51.0"
"@typescript-eslint/typescript-estree" "8.51.0"
"@typescript-eslint/visitor-keys" "8.51.0"
"@typescript-eslint/tsconfig-utils" "^8.49.0"
"@typescript-eslint/types" "^8.49.0"
debug "^4.3.4"
"@typescript-eslint/project-service@8.50.1":
version "8.50.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.50.1.tgz#3176e55ac2907638f4b8d43da486c864934adc8d"
integrity sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==
"@typescript-eslint/scope-manager@8.49.0":
version "8.49.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.49.0.tgz#a3496765b57fb48035d671174552e462e5bffa63"
integrity sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.50.1"
"@typescript-eslint/types" "^8.50.1"
debug "^4.3.4"
"@typescript-eslint/types" "8.49.0"
"@typescript-eslint/visitor-keys" "8.49.0"
"@typescript-eslint/project-service@8.51.0":
version "8.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.51.0.tgz#3cfef313d8bebbf4b2442675a4dd463cef4c8369"
integrity sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==
"@typescript-eslint/tsconfig-utils@8.49.0", "@typescript-eslint/tsconfig-utils@^8.49.0":
version "8.49.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.49.0.tgz#857777c8e35dd1e564505833d8043f544442fbf4"
integrity sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==
"@typescript-eslint/type-utils@8.49.0":
version "8.49.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.49.0.tgz#d8118a0c1896a78a22f01d3c176e9945409b085b"
integrity sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.51.0"
"@typescript-eslint/types" "^8.51.0"
debug "^4.3.4"
"@typescript-eslint/scope-manager@8.50.1":
version "8.50.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz#4a7cd64bcd45990865bdb2bedcacbfeccbd08193"
integrity sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==
dependencies:
"@typescript-eslint/types" "8.50.1"
"@typescript-eslint/visitor-keys" "8.50.1"
"@typescript-eslint/scope-manager@8.51.0":
version "8.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz#19b42f65680c21f7b6f40fe9024327f6bb1893c1"
integrity sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==
dependencies:
"@typescript-eslint/types" "8.51.0"
"@typescript-eslint/visitor-keys" "8.51.0"
"@typescript-eslint/tsconfig-utils@8.50.1", "@typescript-eslint/tsconfig-utils@^8.50.1":
version "8.50.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz#ee4894bec14ef13db305d0323b14b109d996f116"
integrity sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==
"@typescript-eslint/tsconfig-utils@8.51.0", "@typescript-eslint/tsconfig-utils@^8.51.0":
version "8.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz#a575e9885e62dbd260fb64474eff1dae6e317515"
integrity sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==
"@typescript-eslint/type-utils@8.50.1":
version "8.50.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz#7bbc79baa03aee6e3b3faf14bb0b8a78badb2370"
integrity sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==
dependencies:
"@typescript-eslint/types" "8.50.1"
"@typescript-eslint/typescript-estree" "8.50.1"
"@typescript-eslint/utils" "8.50.1"
"@typescript-eslint/types" "8.49.0"
"@typescript-eslint/typescript-estree" "8.49.0"
"@typescript-eslint/utils" "8.49.0"
debug "^4.3.4"
ts-api-utils "^2.1.0"
"@typescript-eslint/types@8.50.1":
version "8.50.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.50.1.tgz#43d19e99613788e0715f799a29f139981bcd8385"
integrity sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==
"@typescript-eslint/types@8.49.0", "@typescript-eslint/types@^8.49.0":
version "8.49.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.49.0.tgz#c1bd3ebf956d9e5216396349ca23c58d74f06aee"
integrity sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==
"@typescript-eslint/types@8.51.0", "@typescript-eslint/types@^8.50.1", "@typescript-eslint/types@^8.51.0":
version "8.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.51.0.tgz#6996e59d49e92fb893531bdc249f0d92a7bebdbb"
integrity sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==
"@typescript-eslint/typescript-estree@8.50.1":
version "8.50.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz#ce273e584694fa5bd34514fcfbea51fe1d79e271"
integrity sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==
"@typescript-eslint/typescript-estree@8.49.0":
version "8.49.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.49.0.tgz#99c5a53275197ccb4e849786dad68344e9924135"
integrity sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==
dependencies:
"@typescript-eslint/project-service" "8.50.1"
"@typescript-eslint/tsconfig-utils" "8.50.1"
"@typescript-eslint/types" "8.50.1"
"@typescript-eslint/visitor-keys" "8.50.1"
"@typescript-eslint/project-service" "8.49.0"
"@typescript-eslint/tsconfig-utils" "8.49.0"
"@typescript-eslint/types" "8.49.0"
"@typescript-eslint/visitor-keys" "8.49.0"
debug "^4.3.4"
minimatch "^9.0.4"
semver "^7.6.0"
tinyglobby "^0.2.15"
ts-api-utils "^2.1.0"
"@typescript-eslint/typescript-estree@8.51.0":
version "8.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz#b57f5157d1ac2127bd7c2c9ad8060fa017df4a1a"
integrity sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==
dependencies:
"@typescript-eslint/project-service" "8.51.0"
"@typescript-eslint/tsconfig-utils" "8.51.0"
"@typescript-eslint/types" "8.51.0"
"@typescript-eslint/visitor-keys" "8.51.0"
debug "^4.3.4"
minimatch "^9.0.4"
semver "^7.6.0"
tinyglobby "^0.2.15"
ts-api-utils "^2.2.0"
"@typescript-eslint/utils@8.50.1":
version "8.50.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.50.1.tgz#054db870952e7526c3cf2162a2ff6e9434e544d0"
integrity sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==
"@typescript-eslint/utils@8.49.0":
version "8.49.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.49.0.tgz#43b3b91d30afd6f6114532cf0b228f1790f43aff"
integrity sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==
dependencies:
"@eslint-community/eslint-utils" "^4.7.0"
"@typescript-eslint/scope-manager" "8.50.1"
"@typescript-eslint/types" "8.50.1"
"@typescript-eslint/typescript-estree" "8.50.1"
"@typescript-eslint/scope-manager" "8.49.0"
"@typescript-eslint/types" "8.49.0"
"@typescript-eslint/typescript-estree" "8.49.0"
"@typescript-eslint/visitor-keys@8.50.1":
version "8.50.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz#13b9d43b7567862faca69527580b9adda1a5c9fd"
integrity sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==
"@typescript-eslint/visitor-keys@8.49.0":
version "8.49.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.49.0.tgz#8e450cc502c0d285cad9e84d400cf349a85ced6c"
integrity sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==
dependencies:
"@typescript-eslint/types" "8.50.1"
eslint-visitor-keys "^4.2.1"
"@typescript-eslint/visitor-keys@8.51.0":
version "8.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz#d37f5c82b9bece2c8aeb3ba7bb836bbba0f92bb8"
integrity sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==
dependencies:
"@typescript-eslint/types" "8.51.0"
"@typescript-eslint/types" "8.49.0"
eslint-visitor-keys "^4.2.1"
"@ungap/structured-clone@^1.0.0":
@@ -4931,10 +4860,10 @@ ansi-styles@^6.1.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
antd@^6.1.2:
version "6.1.2"
resolved "https://registry.yarnpkg.com/antd/-/antd-6.1.2.tgz#99d76a950840c0ca51042507771013463db95a44"
integrity sha512-pqYaZECL/7TBiNxxz+LieLiPCem6DaEzudqN44EZ3SvJjixLP7K41n6clo0zxe/2HiOUe9KxTMxGN+icOkL6Tw==
antd@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/antd/-/antd-6.1.0.tgz#2924f50e37bf1fe9b4c494057eca1c1b7ccfe47a"
integrity sha512-RIe4W5saaL9SWgvqCcvz6LZta/KwT50B0YF7xYiWVZh0Gqfw2rJAsOMcp202Hxgm+YiyoSp4QqqvexKhuGGarw==
dependencies:
"@ant-design/colors" "^8.0.0"
"@ant-design/cssinjs" "^2.0.1"
@@ -4950,8 +4879,8 @@ antd@^6.1.2:
"@rc-component/dialog" "~1.5.1"
"@rc-component/drawer" "~1.3.0"
"@rc-component/dropdown" "~1.0.2"
"@rc-component/form" "~1.6.0"
"@rc-component/image" "~1.5.3"
"@rc-component/form" "~1.4.0"
"@rc-component/image" "~1.5.2"
"@rc-component/input" "~1.1.2"
"@rc-component/input-number" "~1.6.2"
"@rc-component/mentions" "~1.6.0"
@@ -4960,13 +4889,13 @@ antd@^6.1.2:
"@rc-component/mutate-observer" "^2.0.1"
"@rc-component/notification" "~1.2.0"
"@rc-component/pagination" "~1.2.0"
"@rc-component/picker" "~1.9.0"
"@rc-component/picker" "~1.8.0"
"@rc-component/progress" "~1.0.2"
"@rc-component/qrcode" "~1.1.1"
"@rc-component/rate" "~1.0.1"
"@rc-component/resize-observer" "^1.0.1"
"@rc-component/segmented" "~1.3.0"
"@rc-component/select" "~1.3.6"
"@rc-component/segmented" "~1.2.3"
"@rc-component/select" "~1.3.2"
"@rc-component/slider" "~1.0.1"
"@rc-component/steps" "~1.2.2"
"@rc-component/switch" "~1.0.3"
@@ -4977,9 +4906,9 @@ antd@^6.1.2:
"@rc-component/tour" "~2.2.1"
"@rc-component/tree" "~1.1.0"
"@rc-component/tree-select" "~1.4.0"
"@rc-component/trigger" "^3.7.2"
"@rc-component/trigger" "^3.7.1"
"@rc-component/upload" "~1.1.0"
"@rc-component/util" "^1.6.0"
"@rc-component/util" "^1.4.0"
clsx "^2.1.1"
dayjs "^1.11.11"
scroll-into-view-if-needed "^3.1.0"
@@ -5228,10 +5157,10 @@ base64-js@^1.3.1, base64-js@^1.5.1:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
baseline-browser-mapping@^2.9.0:
version "2.9.8"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.8.tgz#04fb5c10ff9c7a1b04ac08cfdfc3b10942a8ac72"
integrity sha512-Y1fOuNDowLfgKOypdc9SPABfoWXuZHBOyCS4cD52IeZBhr4Md6CLLs6atcxVrzRmQ06E7hSlm5bHHApPKR/byA==
baseline-browser-mapping@^2.8.9:
version "2.8.10"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz#32eb5e253d633fa3fa3ffb1685fabf41680d9e8a"
integrity sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==
batch@0.6.1:
version "0.6.1"
@@ -5346,16 +5275,16 @@ browser-assert@^1.2.1:
resolved "https://registry.yarnpkg.com/browser-assert/-/browser-assert-1.2.1.tgz#9aaa5a2a8c74685c2ae05bfe46efd606f068c200"
integrity sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==
browserslist@^4.0.0, browserslist@^4.23.0, browserslist@^4.24.0, browserslist@^4.24.4, browserslist@^4.25.0, browserslist@^4.25.3, browserslist@^4.28.1:
version "4.28.1"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95"
integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==
browserslist@^4.0.0, browserslist@^4.23.0, browserslist@^4.24.0, browserslist@^4.24.4, browserslist@^4.25.0, browserslist@^4.25.3, browserslist@^4.26.3:
version "4.26.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.26.3.tgz#40fbfe2d1cd420281ce5b1caa8840049c79afb56"
integrity sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==
dependencies:
baseline-browser-mapping "^2.9.0"
caniuse-lite "^1.0.30001759"
electron-to-chromium "^1.5.263"
node-releases "^2.0.27"
update-browserslist-db "^1.2.0"
baseline-browser-mapping "^2.8.9"
caniuse-lite "^1.0.30001746"
electron-to-chromium "^1.5.227"
node-releases "^2.0.21"
update-browserslist-db "^1.1.3"
buffer-from@^1.0.0:
version "1.1.2"
@@ -5464,10 +5393,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001762:
version "1.0.30001762"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz#e4dbfeda63d33258cdde93e53af2023a13ba27d4"
integrity sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001746, caniuse-lite@^1.0.30001760:
version "1.0.30001760"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz#bdd1960fafedf8d5f04ff16e81460506ff9b798f"
integrity sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==
ccount@^2.0.0:
version "2.0.1"
@@ -6787,10 +6716,10 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
electron-to-chromium@^1.5.263:
version "1.5.267"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz#5d84f2df8cdb6bfe7e873706bb21bd4bfb574dc7"
integrity sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==
electron-to-chromium@^1.5.227:
version "1.5.228"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz#38b849bc8714bd21fb64f5ad56bf8cfd8638e1e9"
integrity sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==
emoji-regex@^8.0.0:
version "8.0.0"
@@ -6827,10 +6756,10 @@ encodeurl@~2.0.0:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.4:
version "5.18.4"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz#c22d33055f3952035ce6a144ce092447c525f828"
integrity sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==
enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.3:
version "5.18.3"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz#9b5f4c5c076b8787c78fe540392ce76a88855b44"
integrity sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.2.0"
@@ -6956,10 +6885,10 @@ es-iterator-helpers@^1.2.1:
iterator.prototype "^1.1.4"
safe-array-concat "^1.1.3"
es-module-lexer@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.0.0.tgz#f657cd7a9448dcdda9c070a3cb75e5dc1e85f5b1"
integrity sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==
es-module-lexer@^1.2.1:
version "1.7.0"
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a"
integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
version "1.1.1"
@@ -7750,10 +7679,10 @@ globals@^15.14.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8"
integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==
globals@^17.0.0:
version "17.0.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-17.0.0.tgz#a4196d9cfeb4d627ba165b4647b1f5853bf90a30"
integrity sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==
globals@^16.5.0:
version "16.5.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-16.5.0.tgz#ccf1594a437b97653b2be13ed4d8f5c9f850cac1"
integrity sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==
globalthis@^1.0.4:
version "1.0.4"
@@ -8827,16 +8756,7 @@ js-file-download@^0.4.12:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-yaml-loader@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/js-yaml-loader/-/js-yaml-loader-1.2.2.tgz#2c15f93915617acd19676d648945fa3003f8629b"
integrity sha512-H+NeuNrG6uOs/WMjna2SjkaCw13rMWiT/D7l9+9x5n8aq88BDsh2sRmdfxckWPIHtViYHWRG6XiCKYvS1dfyLg==
dependencies:
js-yaml "^3.13.1"
loader-utils "^1.2.3"
un-eval "^1.2.0"
js-yaml@=4.1.1, js-yaml@^4.1.0, js-yaml@^4.1.1:
js-yaml@=4.1.1, js-yaml@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b"
integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
@@ -8844,9 +8764,9 @@ js-yaml@=4.1.1, js-yaml@^4.1.0, js-yaml@^4.1.1:
argparse "^2.0.1"
js-yaml@^3.13.1:
version "3.14.2"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0"
integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==
version "3.14.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
@@ -8915,13 +8835,6 @@ json2mq@^0.2.0:
dependencies:
string-convert "^0.2.0"
json5@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
dependencies:
minimist "^1.2.0"
json5@^2.1.2, json5@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
@@ -9071,15 +8984,6 @@ loader-runner@^4.3.1:
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.1.tgz#6c76ed29b0ccce9af379208299f07f876de737e3"
integrity sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==
loader-utils@^1.2.3:
version "1.4.2"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3"
integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
json5 "^1.0.1"
loader-utils@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
@@ -9456,9 +9360,9 @@ mdast-util-to-hast@^12.1.0:
unist-util-visit "^4.0.0"
mdast-util-to-hast@^13.0.0:
version "13.2.1"
resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz#d7ff84ca499a57e2c060ae67548ad950e689a053"
integrity sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==
version "13.2.0"
resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz#5ca58e5b921cc0a3ded1bc02eed79a4fe4fe41f4"
integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==
dependencies:
"@types/hast" "^3.0.0"
"@types/mdast" "^4.0.0"
@@ -10402,10 +10306,10 @@ node-gyp-build@^4.8.0, node-gyp-build@^4.8.2, node-gyp-build@^4.8.4:
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8"
integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==
node-releases@^2.0.27:
version "2.0.27"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e"
integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==
node-releases@^2.0.21:
version "2.0.21"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.21.tgz#f59b018bc0048044be2d4c4c04e4c8b18160894c"
integrity sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
@@ -11564,9 +11468,9 @@ pupa@^3.1.0:
escape-goat "^4.0.0"
qs@~6.14.0:
version "6.14.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.1.tgz#a41d85b9d3902f31d27861790506294881871159"
integrity sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==
version "6.14.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930"
integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==
dependencies:
side-channel "^1.1.0"
@@ -12881,12 +12785,12 @@ stop-iteration-iterator@^1.1.0:
es-errors "^1.3.0"
internal-slot "^1.1.0"
storybook@^8.6.15:
version "8.6.15"
resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.6.15.tgz#eb4a3bae1ac0fe875bb322a412fac9709d9b5124"
integrity sha512-Ob7DMlwWx8s7dMvcQ3xPc02TvUeralb+xX3oaPRk9wY9Hc6M1IBC/7cEoITkSmRS2v38DHubC+mtEKNc1u2gQg==
storybook@^8.6.11:
version "8.6.14"
resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.6.14.tgz#d205e73b6427eebf321bcfbe63bfbec3ade4d9db"
integrity sha512-sVKbCj/OTx67jhmauhxc2dcr1P+yOgz/x3h0krwjyMgdc5Oubvxyg4NYDZmzAw+ym36g/lzH8N0Ccp4dwtdfxw==
dependencies:
"@storybook/core" "8.6.15"
"@storybook/core" "8.6.14"
string-convert@^0.2.0:
version "0.2.1"
@@ -13215,10 +13119,10 @@ tapable@^2.0.0, tapable@^2.2.0, tapable@^2.2.1, tapable@^2.3.0:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6"
integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==
terser-webpack-plugin@^5.3.16, terser-webpack-plugin@^5.3.9:
version "5.3.16"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz#741e448cc3f93d8026ebe4f7ef9e4afacfd56330"
integrity sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==
terser-webpack-plugin@^5.3.11, terser-webpack-plugin@^5.3.9:
version "5.3.14"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz#9031d48e57ab27567f02ace85c7d690db66c3e06"
integrity sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==
dependencies:
"@jridgewell/trace-mapping" "^0.3.25"
jest-worker "^27.4.5"
@@ -13383,11 +13287,6 @@ ts-api-utils@^2.1.0:
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91"
integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==
ts-api-utils@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.3.0.tgz#9f397ac9d88ac76e8dd6e8bc4af0dbf98af99f73"
integrity sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==
ts-dedent@^2.0.0, ts-dedent@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
@@ -13518,15 +13417,15 @@ types-ramda@^0.30.1:
dependencies:
ts-toolbelt "^9.6.0"
typescript-eslint@^8.50.1:
version "8.50.1"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.50.1.tgz#047df900e568757bc791b6b1ab6fa5fbed9b2393"
integrity sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==
typescript-eslint@^8.49.0:
version "8.49.0"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.49.0.tgz#4a8b608ae48c0db876c8fb2a2724839fc5a7147c"
integrity sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==
dependencies:
"@typescript-eslint/eslint-plugin" "8.50.1"
"@typescript-eslint/parser" "8.50.1"
"@typescript-eslint/typescript-estree" "8.50.1"
"@typescript-eslint/utils" "8.50.1"
"@typescript-eslint/eslint-plugin" "8.49.0"
"@typescript-eslint/parser" "8.49.0"
"@typescript-eslint/typescript-estree" "8.49.0"
"@typescript-eslint/utils" "8.49.0"
typescript@~5.9.3:
version "5.9.3"
@@ -13538,11 +13437,6 @@ ufo@^1.5.4:
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.1.tgz#ac2db1d54614d1b22c1d603e3aef44a85d8f146b"
integrity sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==
un-eval@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/un-eval/-/un-eval-1.2.0.tgz#22a95c650334d59d21697efae32612218ecad65f"
integrity sha512-Wlj/pum6dQtGTPD/lclDtoVPkSfpjPfy1dwnnKw/sZP5DpBH9fLhBgQfsqNhe5/gS1D+vkZUuB771NRMUPA5CA==
unbox-primitive@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2"
@@ -13757,10 +13651,10 @@ unraw@^3.0.0:
resolved "https://registry.yarnpkg.com/unraw/-/unraw-3.0.0.tgz#73443ed70d2ab09ccbac2b00525602d5991fbbe3"
integrity sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==
update-browserslist-db@^1.2.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d"
integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==
update-browserslist-db@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420"
integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==
dependencies:
escalade "^3.2.0"
picocolors "^1.1.1"
@@ -14079,10 +13973,10 @@ webpack-virtual-modules@^0.6.2:
resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8"
integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==
webpack@^5.104.1, webpack@^5.88.1, webpack@^5.95.0:
version "5.104.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.104.1.tgz#94bd41eb5dbf06e93be165ba8be41b8260d4fb1a"
integrity sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==
webpack@^5.103.0, webpack@^5.88.1, webpack@^5.95.0:
version "5.103.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.103.0.tgz#17a7c5a5020d5a3a37c118d002eade5ee2c6f3da"
integrity sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==
dependencies:
"@types/eslint-scope" "^3.7.7"
"@types/estree" "^1.0.8"
@@ -14092,10 +13986,10 @@ webpack@^5.104.1, webpack@^5.88.1, webpack@^5.95.0:
"@webassemblyjs/wasm-parser" "^1.14.1"
acorn "^8.15.0"
acorn-import-phases "^1.0.3"
browserslist "^4.28.1"
browserslist "^4.26.3"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.17.4"
es-module-lexer "^2.0.0"
enhanced-resolve "^5.17.3"
es-module-lexer "^1.2.1"
eslint-scope "5.1.1"
events "^3.2.0"
glob-to-regexp "^0.4.1"
@@ -14106,7 +14000,7 @@ webpack@^5.104.1, webpack@^5.88.1, webpack@^5.95.0:
neo-async "^2.6.2"
schema-utils "^4.3.3"
tapable "^2.3.0"
terser-webpack-plugin "^5.3.16"
terser-webpack-plugin "^5.3.11"
watchpack "^2.4.4"
webpack-sources "^3.3.3"

View File

@@ -334,7 +334,6 @@ select = [
ignore = [
"S101",
"PT004", # Fixtures that don't return values - underscore prefix conflicts with pytest usage
"PT006",
"T201",
"N999",

View File

@@ -1,171 +0,0 @@
#!/usr/bin/env bash
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# -----------------------------------------------------------------------
# Smart docker-compose wrapper for running multiple Superset instances
#
# Features:
# - Auto-generates unique project name from directory
# - Finds available ports automatically
# - No manual .env-local editing needed
#
# Usage:
# ./scripts/docker-compose-up.sh [docker-compose args...]
#
# Examples:
# ./scripts/docker-compose-up.sh # Start all services
# ./scripts/docker-compose-up.sh -d # Start detached
# ./scripts/docker-compose-up.sh down # Stop services
# -----------------------------------------------------------------------
set -e
# Get the repo root directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# Generate project name from directory name (sanitized for Docker)
DIR_NAME=$(basename "$REPO_ROOT")
PROJECT_NAME=$(echo "$DIR_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//')
# Function to check if a port is available
is_port_available() {
local port=$1
if command -v lsof &> /dev/null; then
! lsof -i ":$port" &> /dev/null
elif command -v netstat &> /dev/null; then
! netstat -tuln 2>/dev/null | grep -q ":$port "
elif command -v ss &> /dev/null; then
! ss -tuln 2>/dev/null | grep -q ":$port "
else
# If no tool available, assume port is available
return 0
fi
}
# Track ports we've already claimed in this session
CLAIMED_PORTS=""
# Function to check if port is available and not already claimed
is_port_free() {
local port=$1
# Check not already claimed by us
if [[ " $CLAIMED_PORTS " =~ " $port " ]]; then
return 1
fi
# Check not in use on system
is_port_available $port
}
# Function to find and claim next available port starting from base
# Sets the result in the variable named by $2
find_and_claim_port() {
local base_port=$1
local var_name=$2
local max_attempts=100
local port=$base_port
for ((i=0; i<max_attempts; i++)); do
if is_port_free $port; then
CLAIMED_PORTS="$CLAIMED_PORTS $port"
eval "$var_name=$port"
return 0
fi
((port++))
done
echo "ERROR: Could not find available port starting from $base_port" >&2
return 1
}
# Base ports (defaults from docker-compose.yml)
BASE_NGINX=80
BASE_SUPERSET=8088
BASE_NODE=9000
BASE_WEBSOCKET=8080
BASE_CYPRESS=8081
BASE_DATABASE=5432
BASE_REDIS=6379
# Find available ports (no subshells - claims persist correctly)
echo "🔍 Finding available ports..."
find_and_claim_port $BASE_NGINX NGINX_PORT
find_and_claim_port $BASE_SUPERSET SUPERSET_PORT
find_and_claim_port $BASE_NODE NODE_PORT
find_and_claim_port $BASE_WEBSOCKET WEBSOCKET_PORT
find_and_claim_port $BASE_CYPRESS CYPRESS_PORT
find_and_claim_port $BASE_DATABASE DATABASE_PORT
find_and_claim_port $BASE_REDIS REDIS_PORT
# Export for docker-compose
export COMPOSE_PROJECT_NAME="$PROJECT_NAME"
export NGINX_PORT
export SUPERSET_PORT
export NODE_PORT
export WEBSOCKET_PORT
export CYPRESS_PORT
export DATABASE_PORT
export REDIS_PORT
echo ""
echo "🐳 Starting Superset with:"
echo " Project: $PROJECT_NAME"
echo " Superset: http://localhost:$SUPERSET_PORT"
echo " Dev Server: http://localhost:$NODE_PORT"
echo " Nginx: http://localhost:$NGINX_PORT"
echo " WebSocket: localhost:$WEBSOCKET_PORT"
echo " Database: localhost:$DATABASE_PORT"
echo " Redis: localhost:$REDIS_PORT"
echo ""
# Change to repo root
cd "$REPO_ROOT"
# Handle special commands
case "${1:-}" in
--dry-run)
echo "✅ Dry run complete. To start, run without --dry-run"
exit 0
;;
--env)
# Output as sourceable environment variables
echo "export COMPOSE_PROJECT_NAME='$PROJECT_NAME'"
echo "export NGINX_PORT=$NGINX_PORT"
echo "export SUPERSET_PORT=$SUPERSET_PORT"
echo "export NODE_PORT=$NODE_PORT"
echo "export WEBSOCKET_PORT=$WEBSOCKET_PORT"
echo "export CYPRESS_PORT=$CYPRESS_PORT"
echo "export DATABASE_PORT=$DATABASE_PORT"
echo "export REDIS_PORT=$REDIS_PORT"
exit 0
;;
down|stop|logs|ps|exec|restart)
# Pass through to docker compose
docker compose "$@"
;;
nuke)
# Nuclear option: remove everything (containers, volumes, local images)
echo "💥 Nuking all containers, volumes, and locally-built images for $PROJECT_NAME..."
docker compose down -v --rmi local
echo "✅ Done. Run 'make up' or './scripts/docker-compose-up.sh' to start fresh."
;;
*)
# Default: start services
docker compose up "$@"
;;
esac

View File

@@ -18,7 +18,7 @@
[project]
name = "apache-superset-core"
version = "0.0.1rc3"
version = "0.0.1rc2"
description = "Core Python package for building Apache Superset backend extensions and integrations"
readme = "README.md"
authors = [

View File

@@ -92,11 +92,7 @@ class Database(CoreModel):
"""
Execute SQL synchronously.
The SQL must be written in the dialect of the target database (e.g.,
PostgreSQL syntax for PostgreSQL databases, Snowflake syntax for
Snowflake, etc.). No automatic cross-dialect translation is performed.
:param sql: SQL query to execute (in the target database's dialect)
:param sql: SQL query to execute
:param options: Query execution options (see `QueryOptions`).
If not provided, defaults are used.
:returns: QueryResult with status, data (DataFrame), and metadata
@@ -143,11 +139,7 @@ class Database(CoreModel):
Returns immediately with a handle for tracking progress and retrieving
results from the background worker.
The SQL must be written in the dialect of the target database (e.g.,
PostgreSQL syntax for PostgreSQL databases, Snowflake syntax for
Snowflake, etc.). No automatic cross-dialect translation is performed.
:param sql: SQL query to execute (in the target database's dialect)
:param sql: SQL query to execute
:param options: Query execution options (see `QueryOptions`).
If not provided, defaults are used.
:returns: AsyncQueryHandle for tracking the query

View File

@@ -15,190 +15,49 @@
# specific language governing permissions and limitations
# under the License.
"""
Pydantic models for extension configuration and manifest validation.
Two distinct schemas:
- ExtensionConfig: Source configuration (extension.json) authored by developers
- Manifest: Built output (manifest.json) generated by the build tool
"""
from __future__ import annotations
from typing import Any
from pydantic import BaseModel, Field # noqa: I001
# =============================================================================
# Shared components
# =============================================================================
from typing import TypedDict
class ModuleFederationConfig(BaseModel):
"""Configuration for Webpack Module Federation."""
exposes: list[str] = Field(
default_factory=list,
description="Modules exposed by this extension",
)
filename: str = Field(
default="remoteEntry.js",
description="Remote entry filename",
)
shared: dict[str, Any] = Field(
default_factory=dict,
description="Shared dependencies configuration",
)
remotes: dict[str, str] = Field(
default_factory=dict,
description="Remote module references",
)
class ModuleFederationConfig(TypedDict):
exposes: dict[str, str]
filename: str
shared: dict[str, str]
remotes: dict[str, str]
class ContributionConfig(BaseModel):
"""Configuration for frontend UI contributions."""
commands: list[dict[str, Any]] = Field(
default_factory=list,
description="Command contributions",
)
views: dict[str, list[dict[str, Any]]] = Field(
default_factory=dict,
description="View contributions by location",
)
menus: dict[str, Any] = Field(
default_factory=dict,
description="Menu contributions",
)
class FrontendContributionConfig(TypedDict):
commands: dict[str, list[dict[str, str]]]
views: dict[str, list[dict[str, str]]]
menus: dict[str, list[dict[str, str]]]
class BaseExtension(BaseModel):
"""Base fields shared by ExtensionConfig and Manifest."""
id: str = Field(
...,
description="Unique extension identifier",
min_length=1,
)
name: str = Field(
...,
description="Human-readable extension name",
min_length=1,
)
version: str = Field(
default="0.0.0",
description="Semantic version string",
pattern=r"^\d+\.\d+\.\d+$",
)
license: str | None = Field(
default=None,
description="License identifier (e.g., 'Apache-2.0')",
)
description: str | None = Field(
default=None,
description="Extension description",
)
dependencies: list[str] = Field(
default_factory=list,
description="List of extension IDs this extension depends on",
)
permissions: list[str] = Field(
default_factory=list,
description="Permissions required by this extension",
)
class FrontendManifest(TypedDict):
contributions: FrontendContributionConfig
moduleFederation: ModuleFederationConfig
remoteEntry: str
# =============================================================================
# ExtensionConfig (extension.json)
# =============================================================================
class BackendManifest(TypedDict):
entryPoints: list[str]
class ExtensionConfigFrontend(BaseModel):
"""Frontend section in extension.json."""
contributions: ContributionConfig = Field(
default_factory=ContributionConfig,
description="UI contribution points",
)
moduleFederation: ModuleFederationConfig = Field( # noqa: N815
default_factory=ModuleFederationConfig,
description="Module Federation configuration",
)
class SharedBase(TypedDict, total=False):
id: str
name: str
dependencies: list[str]
description: str
version: str
frontend: FrontendManifest
permissions: list[str]
class ExtensionConfigBackend(BaseModel):
"""Backend section in extension.json."""
entryPoints: list[str] = Field( # noqa: N815
default_factory=list,
description="Python module entry points to load",
)
files: list[str] = Field(
default_factory=list,
description="Glob patterns for backend Python files",
)
class Manifest(SharedBase, total=False):
backend: BackendManifest
class ExtensionConfig(BaseExtension):
"""
Schema for extension.json (source configuration).
This file is authored by developers to define extension metadata.
"""
frontend: ExtensionConfigFrontend | None = Field(
default=None,
description="Frontend configuration",
)
backend: ExtensionConfigBackend | None = Field(
default=None,
description="Backend configuration",
)
class BackendMetadata(BackendManifest):
files: list[str]
# =============================================================================
# Manifest (manifest.json)
# =============================================================================
class ManifestFrontend(BaseModel):
"""Frontend section in manifest.json."""
contributions: ContributionConfig = Field(
default_factory=ContributionConfig,
description="UI contribution points",
)
moduleFederation: ModuleFederationConfig = Field( # noqa: N815
default_factory=ModuleFederationConfig,
description="Module Federation configuration",
)
remoteEntry: str = Field( # noqa: N815
...,
description="Path to the built remote entry file",
)
class ManifestBackend(BaseModel):
"""Backend section in manifest.json."""
entryPoints: list[str] = Field( # noqa: N815
default_factory=list,
description="Python module entry points to load",
)
class Manifest(BaseExtension):
"""
Schema for manifest.json (built output).
This file is generated by the build tool from extension.json.
"""
frontend: ManifestFrontend | None = Field(
default=None,
description="Frontend manifest",
)
backend: ManifestBackend | None = Field(
default=None,
description="Backend manifest",
)
class Metadata(SharedBase):
backend: BackendMetadata

View File

@@ -23,17 +23,12 @@ import sys
import time
import zipfile
from pathlib import Path
from typing import Any, Callable
from typing import Any, Callable, cast
import click
import semver
from jinja2 import Environment, FileSystemLoader
from superset_core.extensions.types import (
ExtensionConfig,
Manifest,
ManifestBackend,
ManifestFrontend,
)
from superset_core.extensions.types import Manifest, Metadata
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
@@ -130,40 +125,40 @@ def clean_dist_frontend(cwd: Path) -> None:
def build_manifest(cwd: Path, remote_entry: str | None) -> Manifest:
extension_data = read_json(cwd / "extension.json")
if not extension_data:
extension: Metadata = cast(Metadata, read_json(cwd / "extension.json"))
if not extension:
click.secho("❌ extension.json not found.", err=True, fg="red")
sys.exit(1)
extension = ExtensionConfig.model_validate(extension_data)
manifest: Manifest = {
"id": extension["id"],
"name": extension["name"],
"version": extension["version"],
"permissions": extension["permissions"],
"dependencies": extension.get("dependencies", []),
}
if (
(frontend := extension.get("frontend"))
and (contributions := frontend.get("contributions"))
and (module_federation := frontend.get("moduleFederation"))
and remote_entry
):
manifest["frontend"] = {
"contributions": contributions,
"moduleFederation": module_federation,
"remoteEntry": remote_entry,
}
frontend: ManifestFrontend | None = None
if extension.frontend and remote_entry:
frontend = ManifestFrontend(
contributions=extension.frontend.contributions,
moduleFederation=extension.frontend.moduleFederation,
remoteEntry=remote_entry,
)
if entry_points := extension.get("backend", {}).get("entryPoints"):
manifest["backend"] = {"entryPoints": entry_points}
backend: ManifestBackend | None = None
if extension.backend and extension.backend.entryPoints:
backend = ManifestBackend(entryPoints=extension.backend.entryPoints)
return Manifest(
id=extension.id,
name=extension.name,
version=extension.version,
permissions=extension.permissions,
dependencies=extension.dependencies,
frontend=frontend,
backend=backend,
)
return manifest
def write_manifest(cwd: Path, manifest: Manifest) -> None:
dist_dir = cwd / "dist"
(dist_dir / "manifest.json").write_text(
manifest.model_dump_json(indent=2, exclude_none=True, by_alias=True)
json.dumps(manifest, indent=2, sort_keys=True)
)
click.secho("✅ Manifest updated", fg="green")
@@ -484,11 +479,6 @@ def init(
(target_dir / "extension.json").write_text(extension_json)
click.secho("✅ Created extension.json", fg="green")
# Create .gitignore
gitignore = env.get_template(".gitignore.j2").render(ctx)
(target_dir / ".gitignore").write_text(gitignore)
click.secho("✅ Created .gitignore", fg="green")
# Initialize frontend files
if include_frontend:
frontend_dir = target_dir / "frontend"

View File

@@ -1,36 +0,0 @@
# Dependencies
node_modules/
# Build outputs
dist/
*.supx
# Python
__pycache__/
*.py[cod]
*$py.class
*.egg-info/
.eggs/
*.egg
.venv/
venv/
env/
ENV/
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
npm-debug.log*
# Environment
.env
.env.local

View File

@@ -236,7 +236,7 @@ def test_build_manifest_creates_correct_manifest_structure(isolated_filesystem):
"permissions": ["read_data"],
"dependencies": ["some_dep"],
"frontend": {
"contributions": {"commands": [{"id": "test_command", "title": "Test"}]},
"contributions": {"commands": ["test_command"]},
"moduleFederation": {"exposes": ["./index"]},
},
"backend": {"entryPoints": ["test_extension.entrypoint"]},
@@ -247,23 +247,23 @@ def test_build_manifest_creates_correct_manifest_structure(isolated_filesystem):
manifest = build_manifest(isolated_filesystem, "remoteEntry.abc123.js")
# Verify manifest structure
assert manifest.id == "test_extension"
assert manifest.name == "Test Extension"
assert manifest.version == "1.0.0"
assert manifest.permissions == ["read_data"]
assert manifest.dependencies == ["some_dep"]
manifest_dict = dict(manifest)
assert manifest_dict["id"] == "test_extension"
assert manifest_dict["name"] == "Test Extension"
assert manifest_dict["version"] == "1.0.0"
assert manifest_dict["permissions"] == ["read_data"]
assert manifest_dict["dependencies"] == ["some_dep"]
# Verify frontend section
assert manifest.frontend is not None
assert manifest.frontend.contributions.commands == [
{"id": "test_command", "title": "Test"}
]
assert manifest.frontend.moduleFederation.exposes == ["./index"]
assert manifest.frontend.remoteEntry == "remoteEntry.abc123.js"
assert "frontend" in manifest
frontend = manifest["frontend"]
assert frontend["contributions"] == {"commands": ["test_command"]}
assert frontend["moduleFederation"] == {"exposes": ["./index"]}
assert frontend["remoteEntry"] == "remoteEntry.abc123.js"
# Verify backend section
assert manifest.backend is not None
assert manifest.backend.entryPoints == ["test_extension.entrypoint"]
assert "backend" in manifest
assert manifest["backend"]["entryPoints"] == ["test_extension.entrypoint"]
@pytest.mark.unit
@@ -280,13 +280,14 @@ def test_build_manifest_handles_minimal_extension(isolated_filesystem):
manifest = build_manifest(isolated_filesystem, None)
assert manifest.id == "minimal_extension"
assert manifest.name == "Minimal Extension"
assert manifest.version == "0.1.0"
assert manifest.permissions == []
assert manifest.dependencies == [] # Default empty list
assert manifest.frontend is None
assert manifest.backend is None
manifest_dict = dict(manifest)
assert manifest_dict["id"] == "minimal_extension"
assert manifest_dict["name"] == "Minimal Extension"
assert manifest_dict["version"] == "0.1.0"
assert manifest_dict["permissions"] == []
assert manifest_dict["dependencies"] == [] # Default empty list
assert "frontend" not in manifest
assert "backend" not in manifest
@pytest.mark.unit

View File

@@ -23,7 +23,6 @@ import time
from unittest.mock import Mock, patch
import pytest
from superset_core.extensions.types import Manifest
from superset_extensions_cli.cli import app, FrontendChangeHandler
@@ -49,7 +48,7 @@ def test_dev_command_starts_watchers(
"""Test dev command starts file watchers."""
# Setup mocks
mock_rebuild_frontend.return_value = "remoteEntry.abc123.js"
mock_build_manifest.return_value = Manifest(id="test", name="test", version="1.0.0")
mock_build_manifest.return_value = {"name": "test", "version": "1.0.0"}
mock_observer = Mock()
mock_observer_class.return_value = mock_observer
@@ -101,7 +100,7 @@ def test_dev_command_initial_build(
"""Test dev command performs initial build setup."""
# Setup mocks
mock_rebuild_frontend.return_value = "remoteEntry.abc123.js"
mock_build_manifest.return_value = Manifest(id="test", name="test", version="1.0.0")
mock_build_manifest.return_value = {"name": "test", "version": "1.0.0"}
extension_setup_for_dev(isolated_filesystem)
@@ -189,12 +188,11 @@ def test_frontend_watcher_function_coverage(isolated_filesystem):
dist_dir = isolated_filesystem / "dist"
dist_dir.mkdir()
mock_manifest = Manifest(id="test", name="test", version="1.0.0")
with patch("superset_extensions_cli.cli.rebuild_frontend") as mock_rebuild:
with patch("superset_extensions_cli.cli.build_manifest") as mock_build:
with patch("superset_extensions_cli.cli.write_manifest") as mock_write:
mock_rebuild.return_value = "remoteEntry.abc123.js"
mock_build.return_value = mock_manifest
mock_build.return_value = {"name": "test", "version": "1.0.0"}
# Simulate frontend watcher function logic
frontend_dir = isolated_filesystem / "frontend"
@@ -211,7 +209,9 @@ def test_frontend_watcher_function_coverage(isolated_filesystem):
mock_build.assert_called_once_with(
isolated_filesystem, "remoteEntry.abc123.js"
)
mock_write.assert_called_once_with(isolated_filesystem, mock_manifest)
mock_write.assert_called_once_with(
isolated_filesystem, {"name": "test", "version": "1.0.0"}
)
@pytest.mark.unit

View File

@@ -296,34 +296,10 @@ def test_init_command_output_messages(cli_runner, isolated_filesystem, cli_input
output = result.output
# Check for expected success messages
assert "Created extension.json" in output
assert "Created .gitignore" in output
assert "Created frontend folder structure" in output
assert "Created backend folder structure" in output
assert "Extension Test Extension (ID: test_extension) initialized" in output
@pytest.mark.cli
def test_gitignore_content_is_correct(cli_runner, isolated_filesystem, cli_input_both):
"""Test that the generated .gitignore has the correct content."""
result = cli_runner.invoke(app, ["init"], input=cli_input_both)
assert result.exit_code == 0
extension_path = isolated_filesystem / "test_extension"
gitignore_path = extension_path / ".gitignore"
assert_file_exists(gitignore_path, ".gitignore")
content = gitignore_path.read_text()
# Verify key patterns are present
assert "node_modules/" in content
assert "dist/" in content
assert "*.supx" in content
assert "__pycache__" in content
assert ".venv/" in content
assert ".DS_Store" in content
assert ".env" in content
assert "Created extension.json" in output
assert "Created frontend folder structure" in output
assert "Created backend folder structure" in output
assert "🎉 Extension Test Extension (ID: test_extension) initialized" in output
@pytest.mark.cli

View File

@@ -191,7 +191,7 @@ def create_test_extension_structure(
Dictionary with expected paths and metadata
"""
extension_path = base_path / id_
expected_files = ["extension.json", ".gitignore"]
expected_files = ["extension.json"]
expected_dirs: list[str] = []
if include_frontend:

View File

@@ -4761,19 +4761,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -5546,20 +5533,6 @@
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -5628,57 +5601,12 @@
"is-arrayish": "^0.2.1"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-module-lexer": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz",
"integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==",
"peer": true
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es6-error": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
@@ -6511,42 +6439,18 @@
}
},
"node_modules/form-data": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz",
"integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==",
"license": "MIT",
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.35",
"safe-buffer": "^5.2.1"
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 0.12"
}
},
"node_modules/form-data/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
@@ -6599,13 +6503,9 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/functional-red-black-tree": {
"version": "1.0.1",
@@ -6640,24 +6540,13 @@
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -6671,19 +6560,6 @@
"node": ">=8.0.0"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
@@ -6793,18 +6669,6 @@
"node": ">= 4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/got": {
"version": "11.8.6",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
@@ -6838,7 +6702,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"peer": true,
"dependencies": {
"function-bind": "^1.1.1"
},
@@ -6855,25 +6718,9 @@
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
"engines": {
"node": ">= 0.4"
},
@@ -6893,18 +6740,6 @@
"node": ">=8"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hast-util-from-parse5": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz",
@@ -7551,10 +7386,9 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"license": "MIT",
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@@ -7979,15 +7813,6 @@
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.3.8.tgz",
"integrity": "sha512-9FbRY3i6U+CbHgrdNbAUaisjWTozkm1ZfupYQJiZ87NtYHk2Zh9DvxMgp/fifxVhqTLpd5fCCLossUbpZxGeKw=="
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mdast-util-definitions": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz",
@@ -13116,7 +12941,7 @@
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "^2.3.4",
"form-data": "~2.3.2",
"http-signature": "~1.3.6",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
@@ -14783,15 +14608,6 @@
"get-intrinsic": "^1.0.2"
}
},
"call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"requires": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
}
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -15360,16 +15176,6 @@
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg=="
},
"dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"requires": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
}
},
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -15432,41 +15238,12 @@
"is-arrayish": "^0.2.1"
}
},
"es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
},
"es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
},
"es-module-lexer": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz",
"integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==",
"peer": true
},
"es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"requires": {
"es-errors": "^1.3.0"
}
},
"es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"requires": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
}
},
"es6-error": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
@@ -16089,23 +15866,13 @@
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="
},
"form-data": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz",
"integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==",
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.35",
"safe-buffer": "^5.2.1"
},
"dependencies": {
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
}
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
}
},
"formdata-polyfill": {
@@ -16150,9 +15917,9 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"functional-red-black-tree": {
"version": "1.0.1",
@@ -16177,20 +15944,13 @@
"integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ=="
},
"get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"requires": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1"
}
},
"get-package-type": {
@@ -16198,15 +15958,6 @@
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="
},
"get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"requires": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
}
},
"get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
@@ -16291,11 +16042,6 @@
}
}
},
"gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
},
"got": {
"version": "11.8.6",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
@@ -16323,7 +16069,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"peer": true,
"requires": {
"function-bind": "^1.1.1"
}
@@ -16334,17 +16079,9 @@
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
},
"has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"requires": {
"has-symbols": "^1.0.3"
}
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
},
"hasha": {
"version": "5.2.2",
@@ -16355,14 +16092,6 @@
"type-fest": "^0.8.0"
}
},
"hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"requires": {
"function-bind": "^1.1.2"
}
},
"hast-util-from-parse5": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz",
@@ -16833,9 +16562,9 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-yaml": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@@ -17170,11 +16899,6 @@
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.3.8.tgz",
"integrity": "sha512-9FbRY3i6U+CbHgrdNbAUaisjWTozkm1ZfupYQJiZ87NtYHk2Zh9DvxMgp/fifxVhqTLpd5fCCLossUbpZxGeKw=="
},
"math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
},
"mdast-util-definitions": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz",

View File

@@ -33,10 +33,5 @@
"cypress": "^11.2.0",
"eslint-plugin-cypress": "^3.5.0",
"tscw-config": "^1.1.2"
},
"overrides": {
"cypress": {
"form-data": "^2.3.4"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -160,7 +160,7 @@
"geostyler-openlayers-parser": "^4.3.0",
"geostyler-style": "7.5.0",
"geostyler-wfs-parser": "^2.0.3",
"googleapis": "^169.0.0",
"googleapis": "^168.0.0",
"immer": "^11.0.1",
"interweave": "^13.1.1",
"jquery": "^3.7.1",
@@ -175,10 +175,10 @@
"memoize-one": "^5.2.1",
"mousetrap": "^1.6.5",
"mustache": "^4.2.0",
"nanoid": "^5.1.6",
"nanoid": "^5.0.9",
"ol": "^7.5.2",
"prop-types": "^15.8.1",
"re-resizable": "^6.11.2",
"re-resizable": "^6.10.1",
"react": "^17.0.2",
"react-checkbox-tree": "^1.8.0",
"react-diff-viewer-continued": "^3.4.0",
@@ -234,7 +234,7 @@
"@babel/preset-typescript": "^7.26.0",
"@babel/register": "^7.23.7",
"@babel/runtime": "^7.28.4",
"@babel/runtime-corejs3": "^7.28.4",
"@babel/runtime-corejs3": "^7.28.2",
"@babel/types": "^7.26.9",
"@cypress/react": "^8.0.2",
"@emotion/babel-plugin": "^11.13.5",
@@ -242,8 +242,8 @@
"@hot-loader/react-dom": "^17.0.2",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@mihkeleidast/storybook-addon-source": "^1.0.1",
"@playwright/test": "^1.57.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
"@playwright/test": "^1.56.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.17",
"@storybook/addon-actions": "8.6.14",
"@storybook/addon-controls": "8.6.14",
"@storybook/addon-essentials": "8.6.14",
@@ -269,15 +269,18 @@
"@types/jest": "^30.0.0",
"@types/js-levenshtein": "^1.1.3",
"@types/json-bigint": "^1.0.4",
"@types/math-expression-evaluator": "^2.0.0",
"@types/mousetrap": "^1.6.15",
"@types/node": "^25.0.3",
"@types/node": "^25.0.2",
"@types/react": "^17.0.83",
"@types/react-dom": "^17.0.26",
"@types/react-json-tree": "^0.13.0",
"@types/react-loadable": "^5.5.11",
"@types/react-redux": "^7.1.10",
"@types/react-resizable": "^3.0.8",
"@types/react-router-dom": "^5.3.3",
"@types/react-transition-group": "^4.4.12",
"@types/react-virtualized-auto-sizer": "^1.0.8",
"@types/react-window": "^1.8.8",
"@types/redux-localstorage": "^1.0.8",
"@types/redux-mock-store": "^1.0.6",
@@ -292,7 +295,7 @@
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
"babel-plugin-lodash": "^3.3.4",
"babel-plugin-typescript-to-proptypes": "^2.0.0",
"baseline-browser-mapping": "^2.9.9",
"baseline-browser-mapping": "^2.9.7",
"cheerio": "1.1.0",
"concurrently": "^9.2.1",
"copy-webpack-plugin": "^13.0.1",
@@ -323,7 +326,7 @@
"fetch-mock": "^11.1.5",
"fork-ts-checker-webpack-plugin": "^9.1.0",
"history": "^5.3.0",
"html-webpack-plugin": "^5.6.5",
"html-webpack-plugin": "^5.6.4",
"http-server": "^14.1.1",
"imports-loader": "^5.0.0",
"jest": "^30.2.0",
@@ -347,7 +350,7 @@
"source-map": "^0.7.4",
"source-map-support": "^0.5.21",
"speed-measure-webpack-plugin": "^1.5.0",
"storybook": "8.6.15",
"storybook": "8.6.14",
"style-loader": "^4.0.0",
"swc-loader": "^0.2.6",
"terser-webpack-plugin": "^5.3.16",
@@ -360,7 +363,7 @@
"vm-browserify": "^1.1.2",
"wait-on": "^9.0.3",
"webpack": "^5.103.0",
"webpack-bundle-analyzer": "^5.1.0",
"webpack-bundle-analyzer": "^4.10.1",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.2",
"webpack-manifest-plugin": "^5.0.1",
@@ -383,7 +386,7 @@
"d3-color": "^3.1.0",
"puppeteer": "^22.4.1",
"underscore": "^1.13.7",
"jspdf": "^3.0.2",
"jspdf": "^3.0.1",
"nwsapi": "^2.2.13",
"@deck.gl/aggregation-layers": "~9.2.2",
"@deck.gl/core": "~9.2.2",

View File

@@ -1,6 +1,6 @@
{
"name": "@apache-superset/core",
"version": "0.0.1-rc6",
"version": "0.0.1-rc5",
"description": "This package contains UI elements, APIs, and utility functions used by Superset.",
"sideEffects": false,
"main": "lib/index.js",
@@ -17,7 +17,7 @@
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.26.0",
"install": "^0.13.0",
"npm": "^11.7.0",
"npm": "^11.1.0",
"typescript": "^5.0.0",
"@emotion/styled": "^11.14.1",
"@types/lodash": "^4.17.21",

View File

@@ -32,7 +32,7 @@
"ag-grid-community": "34.3.1",
"ag-grid-react": "34.3.1",
"brace": "^0.11.1",
"classnames": "^2.5.1",
"classnames": "^2.2.5",
"csstype": "^3.1.3",
"core-js": "^3.38.1",
"d3-format": "^1.3.2",
@@ -78,7 +78,8 @@
"@types/react-syntax-highlighter": "^15.5.13",
"@types/jquery": "^3.5.33",
"@types/lodash": "^4.17.21",
"@types/node": "^25.0.3",
"@types/math-expression-evaluator": "^2.0.0",
"@types/node": "^25.0.2",
"@types/prop-types": "^15.7.15",
"@types/rison": "0.1.0",
"@types/seedrandom": "^3.0.8",

View File

@@ -89,15 +89,11 @@ export function EditableTitle({
onEditingChange,
...rest
}: EditableTitleProps) {
const [isEditingInternal, setIsEditingInternal] = useState(editing);
// Use editing prop directly when provided, otherwise use internal state
const isEditing = editing || isEditingInternal;
const setIsEditing = setIsEditingInternal;
const [isEditing, setIsEditing] = useState(editing);
const [currentTitle, setCurrentTitle] = useState(title);
const [lastTitle, setLastTitle] = useState(title);
const [inputWidth, setInputWidth] = useState<number>(0);
const contentRef = useRef<TextAreaRef>(null);
const prevIsEditingRef = useRef(isEditing);
function measureTextWidth(text: string, font = '14px Arial') {
const canvas = document.createElement('canvas');
@@ -126,13 +122,6 @@ export function EditableTitle({
}
}, [title]);
// Sync internal state when editing prop changes (for controlled mode)
useEffect(() => {
if (editing) {
setIsEditingInternal(true);
}
}, [editing]);
useEffect(() => {
if (isEditing && contentRef.current) {
const textArea = contentRef.current.resizableTextArea?.textArea;
@@ -143,11 +132,7 @@ export function EditableTitle({
textArea.scrollTop = textArea.scrollHeight;
}
}
// Notify parent of editing state changes
if (prevIsEditingRef.current !== isEditing) {
onEditingChange?.(isEditing);
}
prevIsEditingRef.current = isEditing;
onEditingChange?.(isEditing);
}, [isEditing, onEditingChange]);
function handleClick() {

View File

@@ -149,7 +149,6 @@ import {
CompressOutlined,
HistoryOutlined,
SlackOutlined,
ApiOutlined,
} from '@ant-design/icons';
import { FC } from 'react';
import { IconType } from './types';
@@ -288,7 +287,6 @@ const AntdIcons = {
CompressOutlined,
HistoryOutlined,
SlackOutlined,
ApiOutlined,
} as const;
type AntdIconNames = keyof typeof AntdIcons;

View File

@@ -240,86 +240,3 @@ test('should call setSortBy when clicking sortable column header', () => {
},
]);
});
test('should not apply highlight class when highlightRowId is undefined', () => {
const propsWithoutHighlight = {
...defaultProps,
highlightRowId: undefined,
};
const { container } = render(<TableCollection {...propsWithoutHighlight} />);
// Check that no rows have the highlight class
const highlightedRows = container.querySelectorAll('.table-row-highlighted');
expect(highlightedRows).toHaveLength(0);
});
test('should not apply highlight class when highlightRowId is null', () => {
const propsWithNullHighlight = {
...defaultProps,
highlightRowId: null,
};
const { container } = render(<TableCollection {...propsWithNullHighlight} />);
// Check that no rows have the highlight class
const highlightedRows = container.querySelectorAll('.table-row-highlighted');
expect(highlightedRows).toHaveLength(0);
});
test('should apply highlight class only to matching row when highlightRowId is provided', () => {
// Create data where the first row has id: 1 to match highlightRowId: 1
const dataWithIds = [
{
col1: 'Line 01 - Col 01',
col2: 'Line 01 - Col 02',
id: 1, // This should be highlighted
parent: { child: 'Nested Value 1' },
},
{
col1: 'Line 02 - Col 01',
col2: 'Line 02 - Col 02',
id: 2,
parent: { child: 'Nested Value 2' },
},
{
col1: 'Line 03 - Col 01',
col2: 'Line 03 - Col 02',
id: 3,
parent: { child: 'Nested Value 3' },
},
];
// Create new table hook with data that has ids
const { result } = renderHook(() =>
useTable({ columns: tableHook.columns, data: dataWithIds }),
);
const newTableHook = result.current;
const propsWithHighlight = {
...defaultProps,
highlightRowId: 1,
rows: newTableHook.rows,
prepareRow: newTableHook.prepareRow,
};
const { container } = render(<TableCollection {...propsWithHighlight} />);
// Check that only one row has the highlight class
const highlightedRows = container.querySelectorAll('.table-row-highlighted');
expect(highlightedRows).toHaveLength(1);
});
test('should not apply highlight when records have no id field and highlightRowId is undefined', () => {
// This is the key test for the bug fix - use original data without id field
const propsWithNoIds = {
...defaultProps,
highlightRowId: undefined,
};
const { container } = render(<TableCollection {...propsWithNoIds} />);
// Check that no rows have the highlight class (was the bug: all rows were highlighted)
const highlightedRows = container.querySelectorAll('.table-row-highlighted');
expect(highlightedRows).toHaveLength(0);
});

View File

@@ -280,9 +280,7 @@ function TableCollection<T extends object>({
const getRowClassName = useCallback(
(record: Record<string, unknown>) =>
highlightRowId !== undefined && record?.id === highlightRowId
? 'table-row-highlighted'
: '',
record?.id === highlightRowId ? 'table-row-highlighted' : '',
[highlightRowId],
);

View File

@@ -16,7 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
import type { FC } from 'react';
import { css, styled, useTheme } from '@apache-superset/core/ui';
// eslint-disable-next-line no-restricted-imports
@@ -162,9 +161,7 @@ export const StyledLineEditableTabs = styled(EditableTabs)`
}
`;
export const LineEditableTabs: FC<TabsProps> & {
TabPane: typeof StyledTabPane;
} = Object.assign(StyledLineEditableTabs, {
export const LineEditableTabs = Object.assign(StyledLineEditableTabs, {
TabPane: StyledTabPane,
});

View File

@@ -26,7 +26,6 @@ export enum FeatureFlag {
AlertReports = 'ALERT_REPORTS',
AlertReportTabs = 'ALERT_REPORT_TABS',
AlertReportSlackV2 = 'ALERT_REPORT_SLACK_V2',
AlertReportWebhook = 'ALERT_REPORT_WEBHOOK',
AlertReportsFilter = 'ALERT_REPORTS_FILTER',
AllowFullCsvExport = 'ALLOW_FULL_CSV_EXPORT',
AvoidColorsCollision = 'AVOID_COLORS_COLLISION',

View File

@@ -25,7 +25,6 @@ import { createTooltipContent } from '../../utilities/tooltipUtils';
import TooltipRow from '../../TooltipRow';
import { unitToRadius } from '../../utils/geo';
import { HIGHLIGHT_COLOR_ARRAY } from '../../utils';
import { isMetricValue, extractMetricKey } from '../utils/metricUtils';
function getMetricLabel(metric: any) {
if (typeof metric === 'string') {
@@ -45,18 +44,9 @@ function setTooltipContent(
verboseMap?: Record<string, string>,
) {
const defaultTooltipGenerator = (o: JsonObject) => {
// Only show metric info if point_radius_fixed is metric-based
let metricKey = null;
if (isMetricValue(formData.point_radius_fixed)) {
metricKey = extractMetricKey(formData.point_radius_fixed?.value);
}
// Normalize metricKey for verboseMap lookup
const lookupKey = typeof metricKey === 'string' ? metricKey : null;
const label = lookupKey
? verboseMap?.[lookupKey] || getMetricLabel(lookupKey)
: null;
const label =
verboseMap?.[formData.point_radius_fixed.value] ||
getMetricLabel(formData.point_radius_fixed?.value);
return (
<div className="deckgl-tooltip">
<TooltipRow
@@ -69,7 +59,7 @@ function setTooltipContent(
value={`${o.object?.cat_color}`}
/>
)}
{o.object?.metric && label && (
{o.object?.metric && (
<TooltipRow label={`${label}: `} value={`${o.object?.metric}`} />
)}
</div>

View File

@@ -1,312 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import buildQuery, { DeckScatterFormData } from './buildQuery';
const baseFormData: DeckScatterFormData = {
datasource: '1__table',
viz_type: 'deck_scatter',
spatial: {
type: 'latlong',
latCol: 'LATITUDE',
lonCol: 'LONGITUDE',
},
row_limit: 100,
};
test('Scatter buildQuery should not include metric when point_radius_fixed is fixed type', () => {
const formData: DeckScatterFormData = {
...baseFormData,
point_radius_fixed: {
type: 'fix',
value: '1000',
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.metrics).toEqual([]);
expect(query.orderby).toEqual([]);
});
test('Scatter buildQuery should include metric when point_radius_fixed is metric type', () => {
const formData: DeckScatterFormData = {
...baseFormData,
point_radius_fixed: {
type: 'metric',
value: 'AVG(radius_value)',
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.metrics).toContain('AVG(radius_value)');
expect(query.orderby).toEqual([['AVG(radius_value)', false]]);
});
test('Scatter buildQuery should handle numeric value in fixed type', () => {
const formData: DeckScatterFormData = {
...baseFormData,
point_radius_fixed: {
type: 'fix',
value: 500,
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
// Fixed numeric value should not be included as a metric
expect(query.metrics).toEqual([]);
expect(query.orderby).toEqual([]);
});
test('Scatter buildQuery should handle missing point_radius_fixed', () => {
const formData: DeckScatterFormData = {
...baseFormData,
// no point_radius_fixed
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.metrics).toEqual([]);
expect(query.orderby).toEqual([]);
});
test('Scatter buildQuery should include spatial columns in query', () => {
const formData: DeckScatterFormData = {
...baseFormData,
point_radius_fixed: {
type: 'fix',
value: '1000',
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.columns).toContain('LATITUDE');
expect(query.columns).toContain('LONGITUDE');
});
test('Scatter buildQuery should include dimension column when specified', () => {
const formData: DeckScatterFormData = {
...baseFormData,
dimension: 'category',
point_radius_fixed: {
type: 'fix',
value: '1000',
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.columns).toContain('category');
});
test('Scatter buildQuery should add spatial null filters', () => {
const formData: DeckScatterFormData = {
...baseFormData,
point_radius_fixed: {
type: 'fix',
value: '1000',
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
const latFilter = query.filters?.find(
f => f.col === 'LATITUDE' && f.op === 'IS NOT NULL',
);
const lonFilter = query.filters?.find(
f => f.col === 'LONGITUDE' && f.op === 'IS NOT NULL',
);
expect(latFilter).toBeDefined();
expect(lonFilter).toBeDefined();
});
test('Scatter buildQuery should throw error when spatial is missing', () => {
const formData: DeckScatterFormData = {
...baseFormData,
spatial: undefined,
};
expect(() => buildQuery(formData)).toThrow(
'Spatial configuration is required for Scatter charts',
);
});
test('Scatter buildQuery should handle geohash spatial type', () => {
const formData: DeckScatterFormData = {
...baseFormData,
spatial: {
type: 'geohash',
geohashCol: 'geohash_column',
},
point_radius_fixed: {
type: 'metric',
value: 'COUNT(*)',
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.columns).toContain('geohash_column');
expect(query.metrics).toContain('COUNT(*)');
});
test('Scatter buildQuery should handle tooltip_contents', () => {
const formData: DeckScatterFormData = {
...baseFormData,
tooltip_contents: ['name', 'description'],
point_radius_fixed: {
type: 'fix',
value: '1000',
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.columns).toContain('name');
expect(query.columns).toContain('description');
});
test('Scatter buildQuery should handle js_columns', () => {
const formData: DeckScatterFormData = {
...baseFormData,
js_columns: ['custom_col1', 'custom_col2'],
point_radius_fixed: {
type: 'fix',
value: '1000',
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.columns).toContain('custom_col1');
expect(query.columns).toContain('custom_col2');
});
test('Scatter buildQuery should convert numeric metric value to string', () => {
const formData: DeckScatterFormData = {
...baseFormData,
point_radius_fixed: {
type: 'metric',
value: 123, // numeric metric (edge case)
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.metrics).toContain('123');
expect(query.orderby).toEqual([['123', false]]);
});
test('Scatter buildQuery should set is_timeseries to false', () => {
const formData: DeckScatterFormData = {
...baseFormData,
point_radius_fixed: {
type: 'fix',
value: '1000',
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.is_timeseries).toBe(false);
});
test('Scatter buildQuery should preserve row_limit', () => {
const formData: DeckScatterFormData = {
...baseFormData,
row_limit: 5000,
point_radius_fixed: {
type: 'fix',
value: '1000',
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.row_limit).toBe(5000);
});
test('Scatter buildQuery should preserve existing metrics when adding radius metric', () => {
const formData: DeckScatterFormData = {
...baseFormData,
metrics: ['COUNT(*)'],
point_radius_fixed: {
type: 'metric',
value: 'AVG(radius_value)',
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.metrics).toContain('COUNT(*)');
expect(query.metrics).toContain('AVG(radius_value)');
expect(query.metrics).toHaveLength(2);
});
test('Scatter buildQuery should not modify existing metrics for fixed radius', () => {
const formData: DeckScatterFormData = {
...baseFormData,
metrics: ['COUNT(*)', 'SUM(value)'],
point_radius_fixed: {
type: 'fix',
value: '1000',
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.metrics).toEqual(['COUNT(*)', 'SUM(value)']);
});
test('Scatter buildQuery should deduplicate metrics when radius metric already exists', () => {
const formData: DeckScatterFormData = {
...baseFormData,
metrics: ['COUNT(*)', 'AVG(price)'],
point_radius_fixed: {
type: 'metric',
value: 'AVG(price)',
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
// Should not have duplicate AVG(price)
expect(query.metrics).toEqual(['COUNT(*)', 'AVG(price)']);
expect(query.metrics).toHaveLength(2);
});

View File

@@ -34,13 +34,11 @@ import {
processMetricsArray,
addTooltipColumnsToQuery,
} from '../buildQueryUtils';
import { isMetricValue, extractMetricKey } from '../utils/metricUtils';
export interface DeckScatterFormData
extends Omit<SpatialFormData, 'color_picker'>, SqlaFormData {
point_radius_fixed?: {
type?: 'fix' | 'metric';
value?: string | number;
value?: string;
};
multiplier?: number;
point_unit?: string;
@@ -80,29 +78,15 @@ export default function buildQuery(formData: DeckScatterFormData) {
columns = withJsColumns as QueryFormColumn[];
columns = addTooltipColumnsToQuery(columns, tooltip_contents);
// Only add metric if point_radius_fixed is a metric type
const isMetric = isMetricValue(point_radius_fixed);
const metricValue = isMetric
? extractMetricKey(point_radius_fixed?.value)
: null;
// Preserve existing metrics and only add radius metric if it's metric-based
const existingMetrics = baseQueryObject.metrics || [];
const radiusMetrics = processMetricsArray(
metricValue ? [metricValue] : [],
);
// Deduplicate metrics to avoid adding the same metric twice
const metricsSet = new Set([...existingMetrics, ...radiusMetrics]);
const metrics = Array.from(metricsSet);
const metrics = processMetricsArray([point_radius_fixed?.value]);
const filters = addSpatialNullFilters(
spatial,
ensureIsArray(baseQueryObject.filters || []),
);
const orderby =
isMetric && metricValue
? ([[metricValue, false]] as QueryFormOrderBy[])
: (baseQueryObject.orderby as QueryFormOrderBy[]) || [];
const orderby = point_radius_fixed?.value
? ([[point_radius_fixed.value, false]] as QueryFormOrderBy[])
: (baseQueryObject.orderby as QueryFormOrderBy[]) || [];
return [
{

View File

@@ -1,303 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ChartProps, DatasourceType } from '@superset-ui/core';
import transformProps from './transformProps';
interface ScatterFeature {
position: [number, number];
radius?: number;
metric?: number;
cat_color?: string;
extraProps?: Record<string, unknown>;
}
jest.mock('../spatialUtils', () => ({
...jest.requireActual('../spatialUtils'),
getMapboxApiKey: jest.fn(() => 'mock-mapbox-key'),
}));
const mockChartProps: Partial<ChartProps> = {
rawFormData: {
spatial: {
type: 'latlong',
latCol: 'LATITUDE',
lonCol: 'LONGITUDE',
},
viewport: {},
},
queriesData: [
{
data: [
{
LATITUDE: 37.8,
LONGITUDE: -122.4,
population: 50000,
'AVG(radius_value)': 100,
},
{
LATITUDE: 37.9,
LONGITUDE: -122.3,
population: 75000,
'AVG(radius_value)': 200,
},
],
},
],
datasource: {
type: DatasourceType.Table,
id: 1,
name: 'test_datasource',
columns: [],
metrics: [],
},
height: 400,
width: 600,
hooks: {},
filterState: {},
emitCrossFilters: false,
};
test('Scatter transformProps should use fixed radius value when point_radius_fixed type is "fix"', () => {
const fixedProps = {
...mockChartProps,
rawFormData: {
...mockChartProps.rawFormData,
point_radius_fixed: {
type: 'fix',
value: '1000',
},
},
};
const result = transformProps(fixedProps as ChartProps);
const features = result.payload.data.features as ScatterFeature[];
expect(features).toHaveLength(2);
expect(features[0]?.radius).toBe(1000);
expect(features[1]?.radius).toBe(1000);
// metric should not be set for fixed radius
expect(features[0]?.metric).toBeUndefined();
expect(features[1]?.metric).toBeUndefined();
});
test('Scatter transformProps should use metric value for radius when point_radius_fixed type is "metric"', () => {
const metricProps = {
...mockChartProps,
rawFormData: {
...mockChartProps.rawFormData,
point_radius_fixed: {
type: 'metric',
value: 'AVG(radius_value)',
},
},
};
const result = transformProps(metricProps as ChartProps);
const features = result.payload.data.features as ScatterFeature[];
expect(features).toHaveLength(2);
expect(features[0]?.radius).toBe(100);
expect(features[0]?.metric).toBe(100);
expect(features[1]?.radius).toBe(200);
expect(features[1]?.metric).toBe(200);
});
test('Scatter transformProps should handle numeric fixed radius value', () => {
const fixedProps = {
...mockChartProps,
rawFormData: {
...mockChartProps.rawFormData,
point_radius_fixed: {
type: 'fix',
value: 500, // numeric value
},
},
};
const result = transformProps(fixedProps as ChartProps);
const features = result.payload.data.features as ScatterFeature[];
expect(features).toHaveLength(2);
expect(features[0]?.radius).toBe(500);
expect(features[1]?.radius).toBe(500);
});
test('Scatter transformProps should handle missing point_radius_fixed', () => {
const propsWithoutRadius = {
...mockChartProps,
rawFormData: {
...mockChartProps.rawFormData,
// no point_radius_fixed
},
};
const result = transformProps(propsWithoutRadius as ChartProps);
const features = result.payload.data.features as ScatterFeature[];
expect(features).toHaveLength(2);
// radius should not be set
expect(features[0]?.radius).toBeUndefined();
expect(features[1]?.radius).toBeUndefined();
});
test('Scatter transformProps should handle dimension for category colors', () => {
const propsWithDimension = {
...mockChartProps,
rawFormData: {
...mockChartProps.rawFormData,
dimension: 'category',
point_radius_fixed: {
type: 'fix',
value: '1000',
},
},
queriesData: [
{
data: [
{
LATITUDE: 37.8,
LONGITUDE: -122.4,
category: 'A',
},
{
LATITUDE: 37.9,
LONGITUDE: -122.3,
category: 'B',
},
],
},
],
};
const result = transformProps(propsWithDimension as ChartProps);
const features = result.payload.data.features as ScatterFeature[];
expect(features).toHaveLength(2);
expect(features[0]?.cat_color).toBe('A');
expect(features[1]?.cat_color).toBe('B');
});
test('Scatter transformProps should not include metric labels for fixed radius', () => {
const fixedProps = {
...mockChartProps,
rawFormData: {
...mockChartProps.rawFormData,
point_radius_fixed: {
type: 'fix',
value: '1000',
},
},
};
const result = transformProps(fixedProps as ChartProps);
// metricLabels should be empty for fixed radius
expect(result.payload.data.metricLabels).toEqual([]);
});
test('Scatter transformProps should include metric labels for metric-based radius', () => {
const metricProps = {
...mockChartProps,
rawFormData: {
...mockChartProps.rawFormData,
point_radius_fixed: {
type: 'metric',
value: 'AVG(radius_value)',
},
},
};
const result = transformProps(metricProps as ChartProps);
// metricLabels should include the metric name
expect(result.payload.data.metricLabels).toContain('AVG(radius_value)');
});
test('Scatter transformProps should handle no records', () => {
const noDataProps = {
...mockChartProps,
queriesData: [{ data: [] }],
rawFormData: {
...mockChartProps.rawFormData,
point_radius_fixed: {
type: 'fix',
value: '1000',
},
},
};
const result = transformProps(noDataProps as ChartProps);
const features = result.payload.data.features as ScatterFeature[];
expect(features).toHaveLength(0);
});
test('Scatter transformProps should handle missing spatial configuration gracefully', () => {
const noSpatialProps = {
...mockChartProps,
rawFormData: {
...mockChartProps.rawFormData,
spatial: undefined,
point_radius_fixed: {
type: 'fix',
value: '1000',
},
},
};
const result = transformProps(noSpatialProps as ChartProps);
const features = result.payload.data.features as ScatterFeature[];
expect(features).toHaveLength(0);
});
test('Scatter transformProps should preserve extra properties from records', () => {
const propsWithExtraData = {
...mockChartProps,
rawFormData: {
...mockChartProps.rawFormData,
point_radius_fixed: {
type: 'fix',
value: '1000',
},
},
queriesData: [
{
data: [
{
LATITUDE: 37.8,
LONGITUDE: -122.4,
custom_field: 'value1',
another_field: 123,
},
],
},
],
};
const result = transformProps(propsWithExtraData as ChartProps);
const features = result.payload.data.features as ScatterFeature[];
expect(features).toHaveLength(1);
expect(features[0]).toMatchObject({
custom_field: 'value1',
another_field: 123,
});
});

View File

@@ -26,7 +26,6 @@ import {
addPropertiesToFeature,
} from '../transformUtils';
import { DeckScatterFormData } from './buildQuery';
import { isFixedValue, getFixedValue } from '../utils/metricUtils';
interface ScatterPoint {
position: [number, number];
@@ -44,7 +43,6 @@ function processScatterData(
radiusMetricLabel?: string,
categoryColumn?: string,
jsColumns?: string[],
fixedRadiusValue?: number | string | null,
): ScatterPoint[] {
if (!spatial || !records.length) {
return [];
@@ -74,15 +72,7 @@ function processScatterData(
extraProps: feature.extraProps || {},
};
// Handle radius: either from metric or fixed value
if (fixedRadiusValue != null) {
// Use fixed radius value for all points
const parsedFixedRadius = parseMetricValue(fixedRadiusValue);
if (parsedFixedRadius !== undefined) {
scatterPoint.radius = parsedFixedRadius;
}
} else if (radiusMetricLabel && feature[radiusMetricLabel] != null) {
// Use metric value for radius
if (radiusMetricLabel && feature[radiusMetricLabel] != null) {
const radiusValue = parseMetricValue(feature[radiusMetricLabel]);
if (radiusValue !== undefined) {
scatterPoint.radius = radiusValue;
@@ -108,21 +98,14 @@ export default function transformProps(chartProps: ChartProps) {
const { spatial, point_radius_fixed, dimension, js_columns } =
formData as DeckScatterFormData;
// Check if this is a fixed value or metric
const fixedRadiusValue = isFixedValue(point_radius_fixed)
? getFixedValue(point_radius_fixed)
: null;
const radiusMetricLabel = getMetricLabelFromFormData(point_radius_fixed);
const records = getRecordsFromQuery(chartProps.queriesData);
const features = processScatterData(
records,
spatial,
radiusMetricLabel,
dimension,
js_columns,
fixedRadiusValue,
);
return createBaseTransformResult(

View File

@@ -1,184 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { getMetricLabel } from '@superset-ui/core';
import { getMetricLabelFromFormData, parseMetricValue } from './transformUtils';
jest.mock('@superset-ui/core', () => ({
...jest.requireActual('@superset-ui/core'),
getMetricLabel: jest.fn((metric: string) => metric),
}));
beforeEach(() => {
jest.clearAllMocks();
});
test('getMetricLabelFromFormData should return undefined for undefined input', () => {
const result = getMetricLabelFromFormData(undefined);
expect(result).toBeUndefined();
});
test('getMetricLabelFromFormData should return undefined for null input', () => {
const result = getMetricLabelFromFormData(null as any);
expect(result).toBeUndefined();
});
test('getMetricLabelFromFormData should handle string metric directly', () => {
const result = getMetricLabelFromFormData('AVG(value)');
expect(result).toBe('AVG(value)');
expect(getMetricLabel).toHaveBeenCalledWith('AVG(value)');
});
test('getMetricLabelFromFormData should return undefined for fixed type', () => {
const result = getMetricLabelFromFormData({
type: 'fix',
value: '1000',
});
expect(result).toBeUndefined();
expect(getMetricLabel).not.toHaveBeenCalled();
});
test('getMetricLabelFromFormData should return undefined for fixed type with numeric value', () => {
const result = getMetricLabelFromFormData({
type: 'fix',
value: 1000,
});
expect(result).toBeUndefined();
expect(getMetricLabel).not.toHaveBeenCalled();
});
test('getMetricLabelFromFormData should return metric label for metric type with string value', () => {
const result = getMetricLabelFromFormData({
type: 'metric',
value: 'SUM(amount)',
});
expect(result).toBe('SUM(amount)');
expect(getMetricLabel).toHaveBeenCalledWith('SUM(amount)');
});
test('getMetricLabelFromFormData should handle object metric values', () => {
const result = getMetricLabelFromFormData({
type: 'metric',
value: {
label: 'Total Sales',
sqlExpression: 'SUM(sales)',
},
});
expect(result).toBe('Total Sales');
expect(getMetricLabel).toHaveBeenCalledWith('Total Sales');
});
test('getMetricLabelFromFormData should use sqlExpression if label is missing', () => {
const result = getMetricLabelFromFormData({
type: 'metric',
value: {
sqlExpression: 'COUNT(*)',
},
});
expect(result).toBe('COUNT(*)');
expect(getMetricLabel).toHaveBeenCalledWith('COUNT(*)');
});
test('getMetricLabelFromFormData should use value field as fallback', () => {
const result = getMetricLabelFromFormData({
type: 'metric',
value: {
value: 'AVG(price)',
},
});
expect(result).toBe('AVG(price)');
expect(getMetricLabel).toHaveBeenCalledWith('AVG(price)');
});
test('getMetricLabelFromFormData should handle metric type with numeric value', () => {
const result = getMetricLabelFromFormData({
type: 'metric',
value: 123,
});
expect(result).toBe('123');
expect(getMetricLabel).toHaveBeenCalledWith('123');
});
test('getMetricLabelFromFormData should return undefined for object without type', () => {
const result = getMetricLabelFromFormData({
value: 'AVG(value)',
});
expect(result).toBeUndefined();
});
test('getMetricLabelFromFormData should return undefined for empty object', () => {
const result = getMetricLabelFromFormData({});
expect(result).toBeUndefined();
});
test('getMetricLabelFromFormData should return undefined for metric type without value', () => {
const result = getMetricLabelFromFormData({
type: 'metric',
});
expect(result).toBeUndefined();
});
test('parseMetricValue should parse numeric strings', () => {
expect(parseMetricValue('123')).toBe(123);
expect(parseMetricValue('123.45')).toBe(123.45);
expect(parseMetricValue('0')).toBe(0);
expect(parseMetricValue('-123')).toBe(-123);
});
test('parseMetricValue should handle numbers directly', () => {
expect(parseMetricValue(123)).toBe(123);
expect(parseMetricValue(123.45)).toBe(123.45);
expect(parseMetricValue(0)).toBe(0);
expect(parseMetricValue(-123)).toBe(-123);
});
test('parseMetricValue should return undefined for null', () => {
expect(parseMetricValue(null)).toBeUndefined();
});
test('parseMetricValue should return undefined for undefined', () => {
expect(parseMetricValue(undefined)).toBeUndefined();
});
test('parseMetricValue should return undefined for non-numeric strings', () => {
expect(parseMetricValue('abc')).toBeUndefined();
expect(parseMetricValue('12a34')).toBe(12); // parseFloat returns 12
expect(parseMetricValue('')).toBeUndefined();
});
test('parseMetricValue should handle edge cases', () => {
expect(parseMetricValue('Infinity')).toBe(Infinity);
expect(parseMetricValue('-Infinity')).toBe(-Infinity);
expect(parseMetricValue('NaN')).toBeUndefined();
});
test('parseMetricValue should handle boolean values', () => {
expect(parseMetricValue(true as any)).toBeUndefined();
expect(parseMetricValue(false as any)).toBeUndefined();
});
test('parseMetricValue should handle objects', () => {
expect(parseMetricValue({} as any)).toBeUndefined();
expect(parseMetricValue({ value: 123 } as any)).toBeUndefined();
});
test('parseMetricValue should handle arrays', () => {
expect(parseMetricValue([] as any)).toBeUndefined();
expect(parseMetricValue([123] as any)).toBe(123); // String([123]) = '123'
});

View File

@@ -16,12 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import { ChartProps } from '@superset-ui/core';
import { ChartProps, getMetricLabel } from '@superset-ui/core';
import { getMapboxApiKey, DataRecord } from './spatialUtils';
import {
getMetricLabelFromValue,
FixedOrMetricValue,
} from './utils/metricUtils';
const NOOP = () => {};
@@ -138,7 +134,9 @@ export function addPropertiesToFeature<T extends Record<string, unknown>>(
}
export function getMetricLabelFromFormData(
metric: string | FixedOrMetricValue | undefined | null,
metric: string | { value?: string } | undefined,
): string | undefined {
return getMetricLabelFromValue(metric);
if (!metric) return undefined;
if (typeof metric === 'string') return getMetricLabel(metric);
return metric.value ? getMetricLabel(metric.value) : undefined;
}

View File

@@ -1,121 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { getMetricLabel } from '@superset-ui/core';
import {
isMetricValue,
isFixedValue,
extractMetricKey,
getMetricLabelFromValue,
getFixedValue,
} from './metricUtils';
jest.mock('@superset-ui/core', () => ({
...jest.requireActual('@superset-ui/core'),
getMetricLabel: jest.fn((metric: string) => metric),
}));
beforeEach(() => {
jest.clearAllMocks();
});
test('isMetricValue should identify metric values correctly', () => {
expect(isMetricValue({ type: 'metric', value: 'COUNT(*)' })).toBe(true);
expect(isMetricValue({ type: 'fix', value: '1000' })).toBe(false);
expect(isMetricValue('AVG(value)')).toBe(true); // legacy string format
expect(isMetricValue(undefined)).toBe(false);
expect(isMetricValue(null)).toBe(false);
});
test('isFixedValue should identify fixed values correctly', () => {
expect(isFixedValue({ type: 'fix', value: '1000' })).toBe(true);
expect(isFixedValue({ type: 'metric', value: 'COUNT(*)' })).toBe(false);
expect(isFixedValue('AVG(value)')).toBe(false); // legacy string format
expect(isFixedValue(undefined)).toBe(false);
expect(isFixedValue(null)).toBe(false);
});
test('extractMetricKey should handle string values', () => {
expect(extractMetricKey('COUNT(*)')).toBe('COUNT(*)');
expect(extractMetricKey('')).toBe('');
});
test('extractMetricKey should handle number values', () => {
expect(extractMetricKey(123)).toBe('123');
expect(extractMetricKey(0)).toBe('0');
});
test('extractMetricKey should extract from object properties', () => {
expect(extractMetricKey({ label: 'Total Sales' })).toBe('Total Sales');
expect(extractMetricKey({ sqlExpression: 'SUM(sales)' })).toBe('SUM(sales)');
expect(extractMetricKey({ value: 'AVG(price)' })).toBe('AVG(price)');
expect(
extractMetricKey({ label: 'Label', sqlExpression: 'SQL', value: 'Value' }),
).toBe('Label'); // priority order
});
test('extractMetricKey should handle null/undefined', () => {
expect(extractMetricKey(null)).toBeUndefined();
expect(extractMetricKey(undefined)).toBeUndefined();
expect(extractMetricKey({})).toBeUndefined();
});
test('getMetricLabelFromValue should return label for metric values', () => {
getMetricLabelFromValue({ type: 'metric', value: 'COUNT(*)' });
expect(getMetricLabel).toHaveBeenCalledWith('COUNT(*)');
getMetricLabelFromValue({ type: 'metric', value: { label: 'Total Sales' } });
expect(getMetricLabel).toHaveBeenCalledWith('Total Sales');
});
test('getMetricLabelFromValue should return undefined for fixed values', () => {
const result = getMetricLabelFromValue({ type: 'fix', value: '1000' });
expect(result).toBeUndefined();
expect(getMetricLabel).not.toHaveBeenCalled();
});
test('getMetricLabelFromValue should handle legacy string format', () => {
getMetricLabelFromValue('AVG(value)');
expect(getMetricLabel).toHaveBeenCalledWith('AVG(value)');
});
test('getFixedValue should return value for fixed types', () => {
expect(getFixedValue({ type: 'fix', value: '1000' })).toBe('1000');
expect(getFixedValue({ type: 'fix', value: 500 })).toBe(500);
});
test('getFixedValue should return undefined for metric types', () => {
expect(getFixedValue({ type: 'metric', value: 'COUNT(*)' })).toBeUndefined();
});
test('getFixedValue should return undefined for string values', () => {
expect(getFixedValue('AVG(value)')).toBeUndefined();
});
test('getFixedValue should handle object values', () => {
expect(
getFixedValue({ type: 'fix', value: { label: 'object' } }),
).toBeUndefined();
});
test('getFixedValue should handle missing values', () => {
expect(getFixedValue({ type: 'fix' })).toBeUndefined();
expect(getFixedValue(undefined)).toBeUndefined();
expect(getFixedValue(null)).toBeUndefined();
});

View File

@@ -1,120 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { getMetricLabel } from '@superset-ui/core';
export type MetricFormValue =
| string
| number
| { label?: string; sqlExpression?: string; value?: string }
| undefined
| null;
export interface FixedOrMetricValue {
type?: 'fix' | 'metric';
value?: MetricFormValue;
}
/**
* Checks if a value is configured as a metric (vs fixed value)
*/
export function isMetricValue(
fixedOrMetric: string | FixedOrMetricValue | undefined | null,
): boolean {
if (!fixedOrMetric) return false;
if (typeof fixedOrMetric === 'string') return true;
return fixedOrMetric.type === 'metric';
}
/**
* Checks if a value is configured as a fixed value (vs metric)
*/
export function isFixedValue(
fixedOrMetric: string | FixedOrMetricValue | undefined | null,
): boolean {
if (!fixedOrMetric) return false;
if (typeof fixedOrMetric === 'string') return false;
return fixedOrMetric.type === 'fix';
}
/**
* Extracts the metric key from a metric value object
* Handles label, sqlExpression, and value properties
*/
export function extractMetricKey(value: MetricFormValue): string | undefined {
if (value == null) return undefined;
if (typeof value === 'string') return value;
if (typeof value === 'number') return String(value);
// Handle object metrics (adhoc or saved metrics)
const metricObj = value as {
label?: string;
sqlExpression?: string;
value?: string;
};
return metricObj.label || metricObj.sqlExpression || metricObj.value;
}
/**
* Gets the metric label from a fixed/metric form value
* Returns undefined for fixed values, metric label for metric values
*/
export function getMetricLabelFromValue(
fixedOrMetric: string | FixedOrMetricValue | undefined | null,
): string | undefined {
if (!fixedOrMetric) return undefined;
// Legacy string format - treat as metric
if (typeof fixedOrMetric === 'string') {
return getMetricLabel(fixedOrMetric);
}
// Only return metric label if it's a metric type, not a fixed value
if (isMetricValue(fixedOrMetric) && fixedOrMetric.value) {
const metricKey = extractMetricKey(fixedOrMetric.value);
if (metricKey && typeof metricKey === 'string') {
return getMetricLabel(metricKey);
}
}
return undefined;
}
/**
* Gets the fixed value from a fixed/metric form value
* Returns the value for fixed types, undefined for metrics
*/
export function getFixedValue(
fixedOrMetric: string | FixedOrMetricValue | undefined | null,
): string | number | undefined {
if (!fixedOrMetric || typeof fixedOrMetric === 'string') {
return undefined;
}
if (isFixedValue(fixedOrMetric) && fixedOrMetric.value) {
if (
typeof fixedOrMetric.value === 'string' ||
typeof fixedOrMetric.value === 'number'
) {
return fixedOrMetric.value;
}
}
return undefined;
}

View File

@@ -45,6 +45,7 @@
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "*",
"@testing-library/user-event": "*",
"@types/classnames": "*",
"@types/react": "*",
"match-sorter": "^6.3.3",
"react": "^17.0.2",

View File

@@ -67,22 +67,11 @@ function getValueRange(
alignPositiveNegative: boolean,
data: InputData[],
) {
const nums = data
.map(row => {
const raw = row[key];
return raw instanceof Number ? raw.valueOf() : raw;
})
.filter(
(value): value is number =>
typeof value === 'number' && Number.isFinite(value),
) as number[];
if (nums.length > 0) {
const maxAbs = d3Max(nums.map(Math.abs));
if (alignPositiveNegative) {
return [0, maxAbs ?? 0] as ValueRange;
}
const extent = d3Extent(nums) as ValueRange | undefined;
return extent ?? [0, 0];
if (typeof data?.[0]?.[key] === 'number') {
const nums = data.map(row => row[key]) as number[];
return (
alignPositiveNegative ? [0, d3Max(nums.map(Math.abs))] : d3Extent(nums)
) as ValueRange;
}
return null;
}

View File

@@ -437,21 +437,15 @@ export function getLegendProps(
zoomable = false,
legendState?: LegendState,
padding?: LegendPaddingType,
): LegendComponentOption {
const isHorizontal =
orientation === LegendOrientation.Top ||
orientation === LegendOrientation.Bottom;
const effectiveType =
type === LegendType.Scroll || !isHorizontal ? type : LegendType.Scroll;
const legend: LegendComponentOption = {
): LegendComponentOption | LegendComponentOption[] {
const legend: LegendComponentOption | LegendComponentOption[] = {
orient: [LegendOrientation.Top, LegendOrientation.Bottom].includes(
orientation,
)
? 'horizontal'
: 'vertical',
show,
type: effectiveType,
type,
selected: legendState,
selector: ['all', 'inverse'],
selectorLabel: {

View File

@@ -139,6 +139,7 @@ describe('Gantt transformProps', () => {
legend: expect.objectContaining({
show: true,
type: 'scroll',
selector: ['all', 'inverse'],
}),
tooltip: {
formatter: expect.anything(),

View File

@@ -891,27 +891,14 @@ describe('getLegendProps', () => {
});
});
it('should default plain legends to scroll for bottom orientation', () => {
it('should return the correct props for plain type with bottom orientation', () => {
expect(
getLegendProps(LegendType.Plain, LegendOrientation.Bottom, false, theme),
).toEqual({
show: false,
bottom: 0,
orient: 'horizontal',
type: 'scroll',
...expectedThemeProps,
});
});
it('should default plain legends to scroll for top orientation', () => {
expect(
getLegendProps(LegendType.Plain, LegendOrientation.Top, false, theme),
).toEqual({
show: false,
top: 0,
right: 0,
orient: 'horizontal',
type: 'scroll',
type: 'plain',
...expectedThemeProps,
});
});

View File

@@ -45,6 +45,7 @@
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "*",
"@testing-library/user-event": "*",
"@types/classnames": "*",
"@types/react": "*",
"match-sorter": "^6.3.3",
"react": "^17.0.2",

View File

@@ -344,7 +344,7 @@ function StickyWrap({
style={{
height: bodyHeight,
overflow: 'auto',
scrollbarGutter: hasVerticalScroll ? 'stable' : undefined,
scrollbarGutter: 'stable',
width: maxWidth,
boxSizing: 'border-box',
}}

View File

@@ -385,7 +385,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
const nums = data
?.map(row => row?.[key])
.filter(value => typeof value === 'number') as number[];
if (nums.length > 0) {
if (data && nums.length === data.length) {
return (
alignPositiveNegative
? [0, d3Max(nums.map(Math.abs))]
@@ -958,7 +958,6 @@ export default function TableChart<D extends DataRecord = DataRecord>(
display: block;
top: 0;
${valueRange &&
typeof value === 'number' &&
`
width: ${`${cellWidth({
value: value as number,

View File

@@ -26,7 +26,6 @@ import {
} from '@superset-ui/core/spec';
import { cloneDeep } from 'lodash';
import TableChart, { sanitizeHeaderId } from '../src/TableChart';
import { GenericDataType } from '@apache-superset/core/api/core';
import transformProps from '../src/transformProps';
import DateWithFormatter from '../src/utils/DateWithFormatter';
import testData from './testData';
@@ -804,77 +803,6 @@ describe('plugin-chart-table', () => {
cells = document.querySelectorAll('td');
});
test('render cell bars even when column contains NULL values', () => {
const props = transformProps({
...testData.raw,
queriesData: [
{
...testData.raw.queriesData[0],
colnames: ['category', 'value1', 'value2', 'value3', 'value4'],
coltypes: [
GenericDataType.String,
GenericDataType.Numeric,
GenericDataType.Numeric,
GenericDataType.Numeric,
GenericDataType.Numeric,
],
data: [
{
category: 'Category A',
value1: 10,
value2: 20,
value3: 30,
value4: null,
},
{
category: 'Category B',
value1: 15,
value2: 25,
value3: 35,
value4: 100,
},
{
category: 'Category C',
value1: 18,
value2: 28,
value3: 38,
value4: null,
},
],
},
],
rawFormData: {
...testData.raw.rawFormData,
show_cell_bars: true,
metrics: ['value1', 'value2', 'value3', 'value4'],
},
});
const { container } = render(
ProviderWrapper({
children: <TableChart {...props} sticky={false} />,
}),
);
// Get all cell bars - should exist for both columns with and without NULL values
const cellBars = container.querySelectorAll('div.cell-bar');
// Should have cell bars in all numeric columns, even those with NULL values
// value1, value2, value3 all have 3 values, value4 has 1 non-NULL value
// Total: 3 + 3 + 3 + 1 = 10 cell bars
expect(cellBars.length).toBeGreaterThan(0);
// Specifically check that value4 column (which has NULLs) still renders bars for non-NULL cells
const rows = container.querySelectorAll('tbody tr');
expect(rows.length).toBe(3);
// Row 2 should have a cell bar in value4 column (value: 100)
const row2Cells = rows[1].querySelectorAll('td');
const value4Cell = row2Cells[4]; // 5th column (0-indexed)
const value4Bar = value4Cell.querySelector('div.cell-bar');
expect(value4Bar).toBeTruthy();
});
test('render color with string column color formatter(operator begins with)', () => {
render(
ProviderWrapper({

View File

@@ -28,7 +28,6 @@ import { IntersectionObserver } from './IntersectionObserver';
import { ResizeObserver } from './ResizeObserver';
import setupSupersetClient from './setupSupersetClient';
import CacheStorage from './CacheStorage';
import { TextEncoder, TextDecoder } from 'util';
const exposedProperties = ['window', 'navigator', 'document'];
@@ -54,11 +53,6 @@ g.window.featureFlags ??= {};
g.URL.createObjectURL ??= () => '';
g.caches = new CacheStorage();
// Add shims for TextEncoder and TextDecoder after upgrading jspdf to v3.0.2+
// Source: https://github.com/parallax/jsPDF/issues/3882
g.TextDecoder = TextDecoder;
g.TextEncoder = TextEncoder;
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({

View File

@@ -135,8 +135,7 @@ export { customRender as render };
export { default as userEvent } from '@testing-library/user-event';
export async function selectOption(option: string, selectName?: string) {
// Use findByRole (async) to wait for element to be ready, preventing race conditions on slow CI
const select = await screen.findByRole(
const select = screen.getByRole(
'combobox',
selectName ? { name: selectName } : {},
);

View File

@@ -1098,14 +1098,12 @@ export function reFetchQueryResults(query) {
export function expandTable(table) {
return function (dispatch) {
const sync =
isFeatureEnabled(FeatureFlag.SqllabBackendPersistence) &&
table.initialized
? SupersetClient.post({
endpoint: encodeURI(`/tableschemaview/${table.id}/expanded`),
postPayload: { expanded: true },
})
: Promise.resolve();
const sync = isFeatureEnabled(FeatureFlag.SqllabBackendPersistence)
? SupersetClient.post({
endpoint: encodeURI(`/tableschemaview/${table.id}/expanded`),
postPayload: { expanded: true },
})
: Promise.resolve();
return sync
.then(() => dispatch({ type: EXPAND_TABLE, table }))
@@ -1124,14 +1122,12 @@ export function expandTable(table) {
export function collapseTable(table) {
return function (dispatch) {
const sync =
isFeatureEnabled(FeatureFlag.SqllabBackendPersistence) &&
table.initialized
? SupersetClient.post({
endpoint: encodeURI(`/tableschemaview/${table.id}/expanded`),
postPayload: { expanded: false },
})
: Promise.resolve();
const sync = isFeatureEnabled(FeatureFlag.SqllabBackendPersistence)
? SupersetClient.post({
endpoint: encodeURI(`/tableschemaview/${table.id}/expanded`),
postPayload: { expanded: false },
})
: Promise.resolve();
return sync
.then(() => dispatch({ type: COLLAPSE_TABLE, table }))

View File

@@ -934,10 +934,6 @@ describe('async actions', () => {
fetchMock.delete(updateTableSchemaEndpoint, {});
fetchMock.post(updateTableSchemaEndpoint, JSON.stringify({ id: 1 }));
const updateTableSchemaExpandedEndpoint =
'glob:**/tableschemaview/*/expanded';
fetchMock.post(updateTableSchemaExpandedEndpoint, {});
const getTableMetadataEndpoint =
'glob:**/api/v1/database/*/table_metadata/*';
fetchMock.get(getTableMetadataEndpoint, {});
@@ -1415,10 +1411,10 @@ describe('async actions', () => {
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
describe('expandTable', () => {
test('updates the table schema state in the backend when initialized', () => {
test('updates the table schema state in the backend', () => {
expect.assertions(2);
const table = { id: 1, initialized: true };
const table = { id: 1 };
const store = mockStore({});
const expectedActions = [
{
@@ -1428,108 +1424,17 @@ describe('async actions', () => {
];
return store.dispatch(actions.expandTable(table)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
const expandedCalls = fetchMock
.calls()
.filter(
call =>
call[0] &&
call[0].includes('/tableschemaview/') &&
call[0].includes('/expanded'),
);
expect(expandedCalls).toHaveLength(1);
});
});
test('does not call backend when table is not initialized', () => {
expect.assertions(2);
const table = { id: 'yVJPtuSackF', initialized: false };
const store = mockStore({});
const expectedActions = [
{
type: actions.EXPAND_TABLE,
table,
},
];
return store.dispatch(actions.expandTable(table)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
// Check all POST calls to find the expanded endpoint
const expandedCalls = fetchMock
.calls()
.filter(
call =>
call[0] &&
call[0].includes('/tableschemaview/') &&
call[0].includes('/expanded'),
);
expect(expandedCalls).toHaveLength(0);
});
});
test('does not call backend when initialized is undefined', () => {
expect.assertions(2);
const table = { id: 'yVJPtuSackF' };
const store = mockStore({});
const expectedActions = [
{
type: actions.EXPAND_TABLE,
table,
},
];
return store.dispatch(actions.expandTable(table)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
// Check all POST calls to find the expanded endpoint
const expandedCalls = fetchMock
.calls()
.filter(
call =>
call[0] &&
call[0].includes('/tableschemaview/') &&
call[0].includes('/expanded'),
);
expect(expandedCalls).toHaveLength(0);
});
});
test('does not call backend when feature flag is off', () => {
expect.assertions(2);
isFeatureEnabled.mockImplementation(
feature => !(feature === 'SQLLAB_BACKEND_PERSISTENCE'),
);
const table = { id: 1, initialized: true };
const store = mockStore({});
const expectedActions = [
{
type: actions.EXPAND_TABLE,
table,
},
];
return store.dispatch(actions.expandTable(table)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
// Check all POST calls to find the expanded endpoint
const expandedCalls = fetchMock
.calls()
.filter(
call =>
call[0] &&
call[0].includes('/tableschemaview/') &&
call[0].includes('/expanded'),
);
expect(expandedCalls).toHaveLength(0);
isFeatureEnabled.mockRestore();
expect(fetchMock.calls(updateTableSchemaEndpoint)).toHaveLength(1);
});
});
});
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
describe('collapseTable', () => {
test('updates the table schema state in the backend when initialized', () => {
test('updates the table schema state in the backend', () => {
expect.assertions(2);
const table = { id: 1, initialized: true };
const table = { id: 1 };
const store = mockStore({});
const expectedActions = [
{
@@ -1539,95 +1444,7 @@ describe('async actions', () => {
];
return store.dispatch(actions.collapseTable(table)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
const expandedCalls = fetchMock
.calls()
.filter(
call =>
call[0] &&
call[0].includes('/tableschemaview/') &&
call[0].includes('/expanded'),
);
expect(expandedCalls).toHaveLength(1);
});
});
test('does not call backend when table is not initialized', () => {
expect.assertions(2);
const table = { id: 'yVJPtuSackF', initialized: false };
const store = mockStore({});
const expectedActions = [
{
type: actions.COLLAPSE_TABLE,
table,
},
];
return store.dispatch(actions.collapseTable(table)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
const expandedCalls = fetchMock
.calls()
.filter(
call =>
call[0] &&
call[0].includes('/tableschemaview/') &&
call[0].includes('/expanded'),
);
expect(expandedCalls).toHaveLength(0);
});
});
test('does not call backend when initialized is undefined', () => {
expect.assertions(2);
const table = { id: 'yVJPtuSackF' };
const store = mockStore({});
const expectedActions = [
{
type: actions.COLLAPSE_TABLE,
table,
},
];
return store.dispatch(actions.collapseTable(table)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
const expandedCalls = fetchMock
.calls()
.filter(
call =>
call[0] &&
call[0].includes('/tableschemaview/') &&
call[0].includes('/expanded'),
);
expect(expandedCalls).toHaveLength(0);
});
});
test('does not call backend when feature flag is off', () => {
expect.assertions(2);
isFeatureEnabled.mockImplementation(
feature => !(feature === 'SQLLAB_BACKEND_PERSISTENCE'),
);
const table = { id: 1, initialized: true };
const store = mockStore({});
const expectedActions = [
{
type: actions.COLLAPSE_TABLE,
table,
},
];
return store.dispatch(actions.collapseTable(table)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
const expandedCalls = fetchMock
.calls()
.filter(
call =>
call[0] &&
call[0].includes('/tableschemaview/') &&
call[0].includes('/expanded'),
);
expect(expandedCalls).toHaveLength(0);
isFeatureEnabled.mockRestore();
expect(fetchMock.calls(updateTableSchemaEndpoint)).toHaveLength(1);
});
});
});

View File

@@ -19,10 +19,9 @@
import { isValidElement } from 'react';
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
import userEvent from '@testing-library/user-event';
import QueryTable from 'src/SqlLab/components/QueryTable';
import { runningQuery, successfulQuery, user } from 'src/SqlLab/fixtures';
import { render, screen, waitFor } from 'spec/helpers/testing-library';
import { render, screen } from 'spec/helpers/testing-library';
const mockedProps = {
queries: [runningQuery, successfulQuery],
@@ -30,11 +29,6 @@ const mockedProps = {
latestQueryId: 'ryhMUZCGb',
};
const queryWithResults = {
...successfulQuery,
resultsKey: 'test-results-key-123',
};
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
describe('QueryTable', () => {
test('is valid', () => {
@@ -98,93 +92,4 @@ describe('QueryTable', () => {
),
).toHaveLength(1);
});
test('renders View button when query has resultsKey', () => {
const mockStore = configureStore([thunk]);
const propsWithResults = {
...mockedProps,
columns: ['started', 'duration', 'rows', 'results'],
queries: [queryWithResults],
};
render(<QueryTable {...propsWithResults} />, {
store: mockStore({ user, sqlLab: { queries: {} } }),
});
expect(screen.getByRole('button', { name: /view/i })).toBeInTheDocument();
});
test('does not render View button when query has no resultsKey', () => {
const mockStore = configureStore([thunk]);
const queryWithoutResults = {
...successfulQuery,
resultsKey: null,
};
const propsWithoutResults = {
...mockedProps,
columns: ['started', 'duration', 'rows', 'results'],
queries: [queryWithoutResults],
};
render(<QueryTable {...propsWithoutResults} />, {
store: mockStore({ user, sqlLab: { queries: {} } }),
});
expect(
screen.queryByRole('button', { name: /view/i }),
).not.toBeInTheDocument();
});
test('clicking View button opens data preview modal', async () => {
const mockStore = configureStore([thunk]);
const propsWithResults = {
...mockedProps,
columns: ['started', 'duration', 'rows', 'results'],
queries: [queryWithResults],
};
render(<QueryTable {...propsWithResults} />, {
store: mockStore({
user,
sqlLab: {
queries: {
[queryWithResults.id]: queryWithResults,
},
},
}),
});
const viewButton = screen.getByRole('button', { name: /view/i });
await userEvent.click(viewButton);
expect(await screen.findByText('Data preview')).toBeInTheDocument();
});
test('modal closes when exiting', async () => {
const mockStore = configureStore([thunk]);
const propsWithResults = {
...mockedProps,
columns: ['started', 'duration', 'rows', 'results'],
queries: [queryWithResults],
};
render(<QueryTable {...propsWithResults} />, {
store: mockStore({
user,
sqlLab: {
queries: {
[queryWithResults.id]: queryWithResults,
},
},
}),
});
const viewButton = screen.getByRole('button', { name: /view/i });
await userEvent.click(viewButton);
expect(await screen.findByText('Data preview')).toBeInTheDocument();
const closeButton = screen.getByRole('button', { name: /close/i });
await userEvent.click(closeButton);
await waitFor(() => {
expect(screen.queryByText('Data preview')).not.toBeInTheDocument();
});
});
});

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { useMemo, ReactNode, useState, useRef } from 'react';
import { useMemo, ReactNode } from 'react';
import {
Card,
Button,
@@ -27,9 +27,9 @@ import {
TableView,
} from '@superset-ui/core/components';
import ProgressBar from '@superset-ui/core/components/ProgressBar';
import { t, QueryResponse, QueryState } from '@superset-ui/core';
import { t, QueryResponse } from '@superset-ui/core';
import { useTheme } from '@apache-superset/core/ui';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import {
queryEditorSetSql,
@@ -37,7 +37,6 @@ import {
fetchQueryResults,
clearQueryResults,
removeQuery,
startQuery,
} from 'src/SqlLab/actions/sqlLab';
import { fDuration, extendedDayjs } from '@superset-ui/core/utils/dates';
import { SqlLabRootState } from 'src/SqlLab/types';
@@ -45,7 +44,7 @@ import { UserWithPermissionsAndRoles as User } from 'src/types/bootstrapTypes';
import { makeUrl } from 'src/utils/pathUtils';
import ResultSet from '../ResultSet';
import HighlightedSql from '../HighlightedSql';
import { StaticPosition, StyledTooltip, ModalResultSetWrapper } from './styles';
import { StaticPosition, StyledTooltip } from './styles';
interface QueryTableQuery extends Omit<
QueryResponse,
@@ -83,15 +82,6 @@ const QueryTable = ({
}: QueryTableProps) => {
const theme = useTheme();
const dispatch = useDispatch();
const [selectedQuery, setSelectedQuery] = useState<QueryResponse | null>(
null,
);
const selectedQueryRef = useRef<QueryResponse | null>(null);
const modalRef = useRef<{
close: () => void;
open: (e: React.MouseEvent) => void;
showModal: boolean;
} | null>(null);
const QUERY_HISTORY_TABLE_HEADERS_LOCALIZED = {
state: t('State'),
@@ -126,14 +116,6 @@ const QueryTable = ({
);
const user = useSelector<SqlLabRootState, User>(state => state.user);
const reduxQueries = useSelector<
SqlLabRootState,
Record<string, QueryResponse>
>(state => state.sqlLab?.queries ?? {}, shallowEqual);
const openAsyncResults = (query: QueryResponse, displayLimit: number) => {
dispatch(fetchQueryResults(query, displayLimit));
};
const data = useMemo(() => {
const restoreSql = (query: QueryResponse) => {
@@ -146,6 +128,10 @@ const QueryTable = ({
dispatch(cloneQueryToNewTab(query, true));
};
const openAsyncResults = (query: QueryResponse, displayLimit: number) => {
dispatch(fetchQueryResults(query, displayLimit));
};
const statusAttributes = {
success: {
config: {
@@ -303,17 +289,26 @@ const QueryTable = ({
);
if (q.resultsKey) {
q.results = (
<Button
buttonSize="xsmall"
buttonStyle="secondary"
onClick={(e: React.MouseEvent) => {
selectedQueryRef.current = query;
setSelectedQuery(query);
modalRef.current?.open(e);
}}
>
{t('View')}
</Button>
<ModalTrigger
className="ResultsModal"
triggerNode={
<Button buttonSize="xsmall" buttonStyle="secondary">
{t('View')}
</Button>
}
modalTitle={t('Data preview')}
beforeOpen={() => openAsyncResults(query, displayLimit)}
onExit={() => dispatch(clearQueryResults(query))}
modalBody={
<ResultSet
showSql
queryId={query.id}
displayLimit={displayLimit}
defaultQueryLimit={1000}
/>
}
responsive
/>
);
} else {
q.results = <></>;
@@ -370,55 +365,6 @@ const QueryTable = ({
return (
<div className="QueryTable">
<ModalTrigger
ref={modalRef}
triggerNode={null}
className="ResultsModal"
modalTitle={t('Data preview')}
beforeOpen={() => {
const query = selectedQueryRef.current;
if (query) {
const existingQuery = reduxQueries[query.id];
if (!existingQuery?.sql && query.sql) {
dispatch(startQuery({ ...query, sql: query.sql }, false));
}
openAsyncResults(query, displayLimit);
}
}}
onExit={() => {
const query = selectedQueryRef.current;
if (query) {
dispatch(clearQueryResults(query));
selectedQueryRef.current = null;
setSelectedQuery(null);
}
}}
modalBody={
selectedQuery ? (
<ModalResultSetWrapper>
{(() => {
const height =
reduxQueries[selectedQuery.id]?.state ===
QueryState.Success &&
reduxQueries[selectedQuery.id]?.results
? Math.floor(window.innerHeight * 0.5)
: undefined;
return (
<ResultSet
showSql
queryId={selectedQuery.id}
displayLimit={displayLimit}
defaultQueryLimit={1000}
useFixedHeight
height={height}
/>
);
})()}
</ModalResultSetWrapper>
) : null
}
responsive
/>
<TableView
columns={columnsOfTable}
data={data}

View File

@@ -39,10 +39,3 @@ export const StyledTooltip = styled(IconTooltip)`
}
}
`;
export const ModalResultSetWrapper = styled.div`
display: flex;
flex-direction: column;
overflow: hidden;
max-height: 50vh;
`;

View File

@@ -104,14 +104,12 @@ export interface ResultSetProps {
csv?: boolean;
database?: Record<string, any>;
displayLimit: number;
height?: number;
queryId: string;
search?: boolean;
showSql?: boolean;
showSqlInline?: boolean;
visualize?: boolean;
defaultQueryLimit: number;
useFixedHeight?: boolean;
}
const ResultContainer = styled.div`
@@ -179,14 +177,12 @@ const ResultSet = ({
csv = true,
database = {},
displayLimit,
height,
queryId,
search = true,
showSql = false,
showSqlInline = false,
visualize = true,
defaultQueryLimit,
useFixedHeight = false,
}: ResultSetProps) => {
const user = useSelector(({ user }: SqlLabRootState) => user, shallowEqual);
const streamingThreshold = useSelector(
@@ -715,16 +711,6 @@ const ResultSet = ({
LocalStorageKeys.SqllabIsRenderHtmlEnabled,
true,
);
const tableProps = {
data,
queryId: query.id,
orderedColumnKeys: results.columns.map(col => col.column_name),
filterText: searchText,
expandedColumns,
allowHTML,
};
return (
<>
<ResultContainer>
@@ -767,21 +753,27 @@ const ResultSet = ({
{sql}
</>
)}
{useFixedHeight && height !== undefined ? (
<ResultTable {...tableProps} height={height} />
) : (
<div
css={css`
flex: 1 1 auto;
`}
>
<AutoSizer disableWidth>
{({ height: autoHeight }) => (
<ResultTable {...tableProps} height={autoHeight} />
)}
</AutoSizer>
</div>
)}
<div
css={css`
flex: 1 1 auto;
`}
>
<AutoSizer disableWidth>
{({ height }) => (
<ResultTable
data={data}
queryId={query.id}
orderedColumnKeys={results.columns.map(
col => col.column_name,
)}
height={height}
filterText={searchText}
expandedColumns={expandedColumns}
allowHTML={allowHTML}
/>
)}
</AutoSizer>
</div>
</ResultContainer>
<StreamingExportModal
visible={showStreamingModal}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Some files were not shown because too many files have changed in this diff Show More