Compare commits
1 Commits
live-edits
...
file-handl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7083d09777 |
@@ -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)"
|
||||
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
1062
CHANGELOG/6.0.0.md
21
Makefile
@@ -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
|
||||
|
||||
@@ -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
@@ -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/)
|
||||
@@ -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/
|
||||
@@ -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 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.|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|
|
||||
|
||||
51
UPDATING.md
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Contribution Types
|
||||
sidebar_position: 5
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
@@ -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
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Deployment
|
||||
sidebar_position: 7
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Development
|
||||
sidebar_position: 6
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: MCP Integration
|
||||
hide_title: true
|
||||
sidebar_position: 8
|
||||
sidebar_position: 7
|
||||
version: 1
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Security
|
||||
sidebar_position: 9
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
@@ -36,7 +36,6 @@ module.exports = {
|
||||
'extensions/overview',
|
||||
'extensions/quick-start',
|
||||
'extensions/architecture',
|
||||
'extensions/dependencies',
|
||||
'extensions/contribution-types',
|
||||
{
|
||||
type: 'category',
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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" )} />
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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: [](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;
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -72,7 +72,6 @@ const sidebars = {
|
||||
'extensions/overview',
|
||||
'extensions/quick-start',
|
||||
'extensions/architecture',
|
||||
'extensions/dependencies',
|
||||
'extensions/contribution-types',
|
||||
{
|
||||
type: 'category',
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -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 && {
|
||||
|
||||
|
Before Width: | Height: | Size: 476 KiB After Width: | Height: | Size: 476 KiB |
BIN
docs/static/img/extensions/gsheets-export.png
vendored
|
Before Width: | Height: | Size: 379 KiB |
BIN
docs/static/img/extensions/parquet-export.png
vendored
|
Before Width: | Height: | Size: 61 KiB |
BIN
docs/static/img/extensions/query-comparison.png
vendored
|
Before Width: | Height: | Size: 597 KiB |
BIN
docs/static/img/extensions/query-estimator.png
vendored
|
Before Width: | Height: | Size: 433 KiB |
BIN
docs/static/img/extensions/result-stats.png
vendored
|
Before Width: | Height: | Size: 394 KiB |
BIN
docs/static/img/extensions/sql-snippets.png
vendored
|
Before Width: | Height: | Size: 358 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
19
docs/static/img/logos/preset.svg
vendored
@@ -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 |
48
docs/static/img/logos/vlmedia.svg
vendored
|
Before Width: | Height: | Size: 10 KiB |
64
docs/static/llms.txt
vendored
@@ -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
|
||||
472
docs/yarn.lock
@@ -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"
|
||||
|
||||
|
||||
@@ -334,7 +334,6 @@ select = [
|
||||
|
||||
ignore = [
|
||||
"S101",
|
||||
"PT004", # Fixtures that don't return values - underscore prefix conflicts with pytest usage
|
||||
"PT006",
|
||||
"T201",
|
||||
"N999",
|
||||
|
||||
@@ -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
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
354
superset-frontend/cypress-base/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5483
superset-frontend/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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],
|
||||
);
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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 [
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
@@ -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(
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -139,6 +139,7 @@ describe('Gantt transformProps', () => {
|
||||
legend: expect.objectContaining({
|
||||
show: true,
|
||||
type: 'scroll',
|
||||
selector: ['all', 'inverse'],
|
||||
}),
|
||||
tooltip: {
|
||||
formatter: expect.anything(),
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -344,7 +344,7 @@ function StickyWrap({
|
||||
style={{
|
||||
height: bodyHeight,
|
||||
overflow: 'auto',
|
||||
scrollbarGutter: hasVerticalScroll ? 'stable' : undefined,
|
||||
scrollbarGutter: 'stable',
|
||||
width: maxWidth,
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 => ({
|
||||
|
||||
@@ -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 } : {},
|
||||
);
|
||||
|
||||
@@ -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 }))
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -39,10 +39,3 @@ export const StyledTooltip = styled(IconTooltip)`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const ModalResultSetWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
max-height: 50vh;
|
||||
`;
|
||||
|
||||
@@ -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}
|
||||
|
||||
BIN
superset-frontend/src/assets/images/pwa/icon-192.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |