Compare commits

..

5 Commits

Author SHA1 Message Date
Beto Dealmeida
503933756e fix: show only filterable columns on filter dropdown 2025-05-01 17:20:02 -04:00
Beto Dealmeida
60261a5dc6 Fix docstring 2025-05-01 12:33:44 -04:00
Beto Dealmeida
456512c508 Fix test 2025-05-01 11:56:13 -04:00
Beto Dealmeida
c1d9b06649 Remove old method 2025-05-01 09:49:50 -04:00
Beto Dealmeida
7a64a82cd9 fix: improve function detection 2025-04-30 16:58:11 -04:00
469 changed files with 13017 additions and 23540 deletions

View File

@@ -1,36 +0,0 @@
# .coveragerc to control coverage.py
[run]
branch = True
source = superset
# omit = bad_file.py
[paths]
source =
superset/
*/site-packages/
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
# Have to re-enable the standard pragma
pragma: no cover
# Don't complain about missing debug-only code:
def __repr__
if self\.debug
# Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:
# Ignore importlib backport
from importlib
if TYPE_CHECKING:
#fail_under = 100
show_missing = True

View File

@@ -27,8 +27,6 @@ updates:
- package-ecosystem: "uv"
directory: "requirements/"
open-pull-requests-limit: 10
schedule:
interval: "weekly"
labels:
- uv
- dependabot

View File

@@ -48,8 +48,6 @@ jobs:
allow-dependencies-licenses: pkg:npm/store2@2.14.2, pkg:npm/applitools/core, pkg:npm/applitools/core-base, pkg:npm/applitools/css-tree, pkg:npm/applitools/ec-client, pkg:npm/applitools/eg-socks5-proxy-server, pkg:npm/applitools/eyes, pkg:npm/applitools/eyes-cypress, pkg:npm/applitools/nml-client, pkg:npm/applitools/tunnel-client, pkg:npm/applitools/utils, pkg:npm/node-forge@1.3.1, pkg:npm/rgbcolor, pkg:npm/jszip@3.10.1
python-dependency-liccheck:
# NOTE: Configuration for liccheck lives in our pyproject.yml.
# You cannot use a liccheck.ini file in this workflow.
runs-on: ubuntu-22.04
steps:
- name: "Checkout Repository"

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-24.04
strategy:
matrix:
python-version: ["current", "previous", "next"]
python-version: ["current", "previous"]
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4

View File

@@ -20,7 +20,7 @@ jobs:
steps:
- uses: actions/checkout@v4
# Do not bump this linkinator-action version without opening
# an ASF Infra ticket to allow the new version first!
# an ASF Infra ticket to allow the new verison first!
- uses: JustinBeckwith/linkinator-action@v1.11.0
continue-on-error: true # This will make the job advisory (non-blocking, no red X)
with:

View File

@@ -77,7 +77,7 @@ jobs:
runs-on: ubuntu-24.04
strategy:
matrix:
python-version: ["current", "previous", "next"]
python-version: ["current", "previous"]
env:
PYTHONPATH: ${{ github.workspace }}
SUPERSET_CONFIG: tests.integration_tests.superset_test_config

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-24.04
strategy:
matrix:
python-version: ["previous", "current", "next"]
python-version: ["previous", "current"]
env:
PYTHONPATH: ${{ github.workspace }}
steps:
@@ -45,13 +45,6 @@ jobs:
SUPERSET_SECRET_KEY: not-a-secret
run: |
pytest --durations-min=0.5 --cov-report= --cov=superset ./tests/common ./tests/unit_tests --cache-clear --maxfail=50
- name: Python 100% coverage unit tests
if: steps.check.outputs.python
env:
SUPERSET_TESTENV: true
SUPERSET_SECRET_KEY: not-a-secret
run: |
pytest --durations-min=0.5 --cov-report= --cov=superset/sql/ ./tests/unit_tests/sql/ --cache-clear --cov-fail-under=100
- name: Upload code coverage
uses: codecov/codecov-action@v5
with:

View File

@@ -100,22 +100,3 @@ repos:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: local
hooks:
- id: pylint
name: pylint with custom Superset plugins
entry: bash
language: system
types: [python]
exclude: ^(tests/|superset/migrations/|scripts/|RELEASING/|docker/)
args:
- -c
- |
TARGET_BRANCH=${GITHUB_BASE_REF:-master}
git fetch origin "$TARGET_BRANCH"
files=$(git diff --name-only --diff-filter=ACM origin/"$TARGET_BRANCH"..HEAD | grep '^superset/.*\.py$' || true)
if [ -n "$files" ]; then
pylint --rcfile=.pylintrc --load-plugins=superset.extensions.pylint $files
else
echo "No Python files to lint."
fi

355
.pylintrc
View File

@@ -1,355 +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.
#
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS,migrations
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
ignore-patterns=
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=superset.extensions.pylint
# Use multiple processes to speed up Pylint.
jobs=2
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code
extension-pkg-whitelist=pyarrow
[MESSAGES CONTROL]
disable=all
enable=disallowed-json-import,disallowed-sql-import,consider-using-transaction
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text
# Tells whether to display a full report or only the messages
reports=yes
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[BASIC]
# Good variable names which should always be accepted, separated by a comma
good-names=_,df,ex,f,i,id,j,k,l,o,pk,Run,ts,v,x,y
# Bad variable names which should always be refused, separated by a comma
bad-names=bar,baz,db,fd,foo,sesh,session,tata,toto,tutu
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Include a hint for the correct naming format with invalid-name
include-naming-hint=no
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
property-classes=
abc.abstractproperty,
sqlalchemy.ext.hybrid.hybrid_property
# Regular expression matching correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct method names
method-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct variable names
variable-rgx=[a-z_][a-z0-9_]{1,30}$
# Regular expression matching correct inline iteration names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Regular expression matching correct constant names
const-rgx=(([A-Za-z_][A-Za-z0-9_]*)|(__.*__))$
# Regular expression matching correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Regular expression matching correct class attribute names
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression matching correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression matching correct attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct function names
function-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=10
[ELIF]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=100
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
[LOGGING]
# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=5
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[SPELLING]
# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package.
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=numpy,pandas,alembic.op,sqlalchemy,alembic.context,flask_appbuilder.security.sqla.PermissionView.role,flask_appbuilder.Model.metadata,flask_appbuilder.Base.metadata
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=contextlib.closing,optparse.Values,thread._local,_thread._local
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,_cb
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,future.builtins
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,_fields,_replace,_source,_make
[DESIGN]
# Maximum number of arguments for function / method
max-args=5
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=10
# Maximum number of branch for function / method body
max-branches=15
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=8
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of boolean expressions in a if statement
max-bool-expr=5
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=optparse
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=builtins.Exception

View File

@@ -18,7 +18,7 @@
######################################################################
# Node stage to deal with static asset construction
######################################################################
ARG PY_VER=3.11.13-slim-bookworm
ARG PY_VER=3.11.11-slim-bookworm
# If BUILDPLATFORM is null, set it to 'amd64' (or leave as is otherwise).
ARG BUILDPLATFORM=${BUILDPLATFORM:-amd64}

View File

@@ -103,7 +103,7 @@ Here are some of the major database solutions that are supported:
<p align="center">
<img src="https://superset.apache.org/img/databases/redshift.png" alt="redshift" border="0" width="200"/>
<img src="https://superset.apache.org/img/databases/google-biquery.png" alt="google-bigquery" border="0" width="200"/>
<img src="https://superset.apache.org/img/databases/google-biquery.png" alt="google-biquery" border="0" width="200"/>
<img src="https://superset.apache.org/img/databases/snowflake.png" alt="snowflake" border="0" width="200"/>
<img src="https://superset.apache.org/img/databases/trino.png" alt="trino" border="0" width="150" />
<img src="https://superset.apache.org/img/databases/presto.png" alt="presto" border="0" width="200"/>
@@ -136,7 +136,7 @@ Here are some of the major database solutions that are supported:
<img src="https://superset.apache.org/img/databases/starrocks.png" alt="starrocks" border="0" width="200" />
<img src="https://superset.apache.org/img/databases/doris.png" alt="doris" border="0" width="200" />
<img src="https://superset.apache.org/img/databases/oceanbase.svg" alt="oceanbase" border="0" width="220" />
<img src="https://superset.apache.org/img/databases/sap-hana.png" alt="sap-hana" border="0" width="220" />
<img src="https://superset.apache.org/img/databases/sap-hana.png" alt="oceanbase" border="0" width="220" />
<img src="https://superset.apache.org/img/databases/denodo.png" alt="denodo" border="0" width="200" />
<img src="https://superset.apache.org/img/databases/ydb.svg" alt="ydb" border="0" width="200" />
<img src="https://superset.apache.org/img/databases/tdengine.png" alt="TDengine" border="0" width="200" />

View File

@@ -43,7 +43,6 @@ Join our growing community!
- [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]
@@ -105,7 +104,6 @@ Join our growing community!
- [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/)

View File

@@ -65,6 +65,8 @@ services:
superset-init:
condition: service_completed_successfully
volumes: *superset-volumes
environment:
SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}"
superset-init:
image: *superset-image
@@ -84,6 +86,9 @@ services:
volumes: *superset-volumes
healthcheck:
disable: true
environment:
SUPERSET_LOAD_EXAMPLES: "${SUPERSET_LOAD_EXAMPLES:-yes}"
SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}"
superset-worker:
image: *superset-image
@@ -106,6 +111,8 @@ services:
"CMD-SHELL",
"celery -A superset.tasks.celery_app:app inspect ping -d celery@$$HOSTNAME",
]
environment:
SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}"
superset-worker-beat:
image: *superset-image
@@ -124,6 +131,8 @@ services:
volumes: *superset-volumes
healthcheck:
disable: true
environment:
SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}"
volumes:
superset_home:

View File

@@ -71,6 +71,8 @@ services:
superset-init:
condition: service_completed_successfully
volumes: *superset-volumes
environment:
SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}"
superset-init:
container_name: superset_init
@@ -91,6 +93,9 @@ services:
volumes: *superset-volumes
healthcheck:
disable: true
environment:
SUPERSET_LOAD_EXAMPLES: "${SUPERSET_LOAD_EXAMPLES:-yes}"
SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}"
superset-worker:
build:
@@ -114,6 +119,8 @@ services:
"CMD-SHELL",
"celery -A superset.tasks.celery_app:app inspect ping -d celery@$$HOSTNAME",
]
environment:
SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}"
superset-worker-beat:
build:
@@ -133,6 +140,8 @@ services:
volumes: *superset-volumes
healthcheck:
disable: true
environment:
SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}"
volumes:
superset_home:

View File

@@ -104,6 +104,9 @@ services:
superset-init:
condition: service_completed_successfully
volumes: *superset-volumes
environment:
CYPRESS_CONFIG: "${CYPRESS_CONFIG:-}"
SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}"
superset-websocket:
container_name: superset_websocket
@@ -155,6 +158,10 @@ services:
condition: service_started
user: *superset-user
volumes: *superset-volumes
environment:
CYPRESS_CONFIG: "${CYPRESS_CONFIG:-}"
SUPERSET_LOAD_EXAMPLES: "${SUPERSET_LOAD_EXAMPLES:-yes}"
SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}"
healthcheck:
disable: true
@@ -199,6 +206,8 @@ services:
required: false
environment:
CELERYD_CONCURRENCY: 2
CYPRESS_CONFIG: "${CYPRESS_CONFIG:-}"
SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}"
restart: unless-stopped
depends_on:
superset-init:
@@ -230,6 +239,9 @@ services:
volumes: *superset-volumes
healthcheck:
disable: true
environment:
CYPRESS_CONFIG: "${CYPRESS_CONFIG:-}"
SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}"
superset-tests-worker:
build:
@@ -250,6 +262,7 @@ services:
REDIS_RESULTS_DB: 3
REDIS_HOST: localhost
CELERYD_CONCURRENCY: 8
SUPERSET_LOG_LEVEL: "${SUPERSET_LOG_LEVEL:-info}"
network_mode: host
depends_on:
superset-init:

View File

@@ -302,15 +302,6 @@ AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "Public"
```
In case you want to assign the `Admin` role on new user registration, it can be assigned as follows:
```python
AUTH_USER_REGISTRATION_ROLE = "Admin"
```
If you encounter the [issue](https://github.com/apache/superset/issues/13243) of not being able to list users from the Superset main page settings, although a newly registered user has an `Admin` role, please re-run `superset init` to sync the required permissions. Below is the command to re-run `superset init` using docker compose.
```
docker-compose exec superset superset init
```
Then, create a `CustomSsoSecurityManager` that extends `SupersetSecurityManager` and overrides
`oauth_user_info`:

View File

@@ -72,8 +72,7 @@ are compatible with Superset.
| [PostgreSQL](/docs/configuration/databases#postgres) | `pip install psycopg2` | `postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
| [Presto](/docs/configuration/databases#presto) | `pip install pyhive` | `presto://{username}:{password}@{hostname}:{port}/{database}` |
| [Rockset](/docs/configuration/databases#rockset) | `pip install rockset-sqlalchemy` | `rockset://<api_key>:@<api_server>` |
| [SAP Hana](/docs/configuration/databases#hana) | `pip install hdbcli sqlalchemy-hana` or `pip install apache_superset[hana]` | `hana://{username}:{password}@{host}:{port}` |
| [SingleStore](/docs/configuration/databases#singlestore) | `pip install sqlalchemy-singlestoredb` | `singlestoredb://{username}:{password}@{host}:{port}/{database}` |
| [SAP Hana](/docs/configuration/databases#hana) | `pip install hdbcli sqlalchemy-hana` or `pip install apache_superset[hana]` | `hana://{username}:{password}@{host}:{port}` |
| [StarRocks](/docs/configuration/databases#starrocks) | `pip install starrocks` | `starrocks://<User>:<Password>@<Host>:<Port>/<Catalog>.<Database>` |
| [Snowflake](/docs/configuration/databases#snowflake) | `pip install snowflake-sqlalchemy` | `snowflake://{user}:{password}@{account}.{region}/{database}?role={role}&warehouse={warehouse}` |
| SQLite | No additional library needed | `sqlite://path/to/file.db?check_same_thread=false` |
@@ -1300,16 +1299,6 @@ You might have noticed that some special charecters are used in the above connec
For more information about this check the [sqlalchemy documentation](https://docs.sqlalchemy.org/en/20/core/engines.html#escaping-special-characters-such-as-signs-in-passwords). Which says `When constructing a fully formed URL string to pass to create_engine(), special characters such as those that may be used in the user and password need to be URL encoded to be parsed correctly. This includes the @ sign.`
:::
#### SingleStore
The recommended connector library for SingleStore is
[sqlalchemy-singlestoredb](https://github.com/singlestore-labs/sqlalchemy-singlestoredb).
The expected connection string is formatted as follows:
```
singlestoredb://{username}:{password}@{host}:{port}/{database}
```
#### StarRocks

View File

@@ -250,14 +250,6 @@ Will be rendered as:
SELECT * FROM users WHERE role IN ('admin', 'viewer')
```
**Current User RLS Rules**
The `{{ current_user_rls_rules() }}` macro returns an array of RLS rules applied to the current dataset for the logged in user.
If you have caching enabled in your Superset configuration, then the list of RLS Rules will be used
by Superset when calculating the cache key. A cache key is a unique identifier that determines if there's a
cache hit in the future and Superset can retrieve cached data.
**Custom URL Parameters**
The `{{ url_param('custom_variable') }}` macro lets you define arbitrary URL

View File

@@ -44,7 +44,7 @@ Here are the build presets that are exposed through the `supersetbot docker` uti
- `py311`, e.g., Py311: Similar to lean but with a different Python version (in this example, 3.11).
- `ci`: For certain CI workloads.
- `websocket`: For Superset clusters supporting advanced features.
- `dockerize`: Used by Helm in initContainers to wait for database dependencies to be available.
- `dockerize`: Used by Helm.
## Key tags examples
@@ -64,56 +64,6 @@ check the [supersetbot docker](https://github.com/apache-superset/supersetbot)
subcommand and the [docker.yml](https://github.com/apache/superset/blob/master/.github/workflows/docker.yml)
GitHub action.
## Building your own production Docker image
Every Superset deployment will require its own set of drivers depending on the data warehouse(s),
etc. so we recommend that users build their own Docker image by extending the `lean` image.
Here's an example Dockerfile that does this. Follow the in-line comments to customize it for
your desired Superset version and database drivers. The comments also note that a certain feature flag will
have to be enabled in your config file.
You would build the image with `docker build -t mysuperset:latest .` or `docker build -t ourcompanysuperset:4.1.2 .`
```Dockerfile
# change this to apache/superset:4.1.2 or whatever version you want to build from;
# otherwise the default is the latest commit on GitHub master branch
FROM apache/superset:master
USER root
# Set environment variable for Playwright
ENV PLAYWRIGHT_BROWSERS_PATH=/usr/local/share/playwright-browsers
# Install packages using uv into the virtual environment
# Superset started using uv after the 4.1 branch; if you are building from apache/superset:4.1.x,
# replace the first two lines with RUN pip install \
RUN . /app/.venv/bin/activate && \
uv pip install \
# install psycopg2 for using PostgreSQL metadata store - could be a MySQL package if using that backend:
psycopg2-binary \
# add the driver(s) for your data warehouse(s), in this example we're showing for Microsoft SQL Server:
pymssql \
# package needed for using single-sign on authentication:
Authlib \
# openpyxl to be able to upload Excel files
openpyxl \
# Pillow for Alerts & Reports to generate PDFs of dashboards
Pillow \
# install Playwright for taking screenshots for Alerts & Reports. This assumes the feature flag PLAYWRIGHT_REPORTS_AND_THUMBNAILS is enabled
# That feature flag will default to True starting in 6.0.0
# Playwright works only with Chrome.
# If you are still using Selenium instead of Playwright, you would instead install here the selenium package and a headless browser & webdriver
playwright \
&& playwright install-deps \
&& PLAYWRIGHT_BROWSERS_PATH=/usr/local/share/playwright-browsers playwright install chromium
# Switch back to the superset user
USER superset
CMD ["/app/docker/entrypoints/run-server.sh"]
```
## Key ARGs in Dockerfile
- `BUILD_TRANSLATIONS`: whether to build the translations into the image. For the

View File

@@ -27,7 +27,9 @@ You will need to back up your metadata DB. That could mean backing up the servic
You will also need to extend the Superset docker image. The default `lean` images do not contain drivers needed to access your metadata database (Postgres or MySQL), nor to access your data warehouse, nor the headless browser needed for Alerts & Reports. You could run a `-dev` image while demoing Superset, which has some of this, but you'll still need to install the driver for your data warehouse. The `-dev` images run as root, which is not recommended for production.
Ideally you will build your own image of Superset that extends `lean`, adding what your deployment needs. See [Building your own production Docker image](/docs/installation/docker-builds/#building-your-own-production-docker-image).
Ideally you will build your own image of Superset that extends `lean`, adding what your deployment needs.
See [Docker Build Presets](/docs/installation/docker-builds/#build-presets) for more information about the different image versions you can extend.
## [Kubernetes (K8s)](/docs/installation/kubernetes.mdx)

View File

@@ -2,13 +2,6 @@
title: CVEs fixed by release
sidebar_position: 2
---
#### Version 4.1.2
| CVE | Title | Affected |
|:---------------|:-----------------------------------------------------------------------------------|---------:|
| CVE-2025-27696 | Improper authorization leading to resource ownership takeover | < 4.1.2 |
| CVE-2025-48912 | Improper authorization bypass on row level security via SQL Injection | < 4.1.2 |
#### Version 4.1.0
| CVE | Title | Affected |

View File

@@ -26,30 +26,30 @@
"@emotion/styled": "^10.0.27",
"@saucelabs/theme-github-codeblock": "^0.3.0",
"@superset-ui/style": "^0.14.23",
"antd": "^5.25.1",
"antd": "^5.24.5",
"docusaurus-plugin-less": "^2.0.2",
"less": "^4.3.0",
"less-loader": "^12.3.0",
"less": "^4.2.2",
"less-loader": "^11.0.0",
"prism-react-renderer": "^2.4.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-github-btn": "^1.4.0",
"react-svg-pan-zoom": "^3.13.1",
"swagger-ui-react": "^5.21.0"
"swagger-ui-react": "^5.20.2"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.7.0",
"@docusaurus/tsconfig": "^3.8.0",
"@docusaurus/tsconfig": "^3.7.0",
"@types/react": "^18.3.12",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.0",
"eslint-config-prettier": "^10.1.5",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react": "^7.0.0",
"prettier": "^2.0.0",
"typescript": "~5.8.3",
"webpack": "^5.99.9"
"typescript": "~5.8.2",
"webpack": "^5.98.0"
},
"browserslist": {
"production": [

File diff suppressed because it is too large Load Diff

View File

@@ -1092,13 +1092,20 @@
core-js-pure "^3.30.2"
regenerator-runtime "^0.14.0"
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.3", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.6", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.4", "@babel/runtime@^7.24.7", "@babel/runtime@^7.24.8", "@babel/runtime@^7.25.7", "@babel/runtime@^7.25.9", "@babel/runtime@^7.26.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4":
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.25.9", "@babel/runtime@^7.8.4":
version "7.27.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762"
integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.6", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.4", "@babel/runtime@^7.24.7", "@babel/runtime@^7.24.8", "@babel/runtime@^7.25.7", "@babel/runtime@^7.26.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2"
integrity sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/template@^7.25.9", "@babel/template@^7.26.9", "@babel/template@^7.27.0":
version "7.27.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.0.tgz#b253e5406cc1df1c57dcd18f11760c2dbf40c0b4"
@@ -1935,10 +1942,10 @@
fs-extra "^11.1.1"
tslib "^2.6.0"
"@docusaurus/tsconfig@^3.8.0":
version "3.8.0"
resolved "https://registry.yarnpkg.com/@docusaurus/tsconfig/-/tsconfig-3.8.0.tgz#ea7ee0917e1562cf0a6e95e049c42f1f61351f32"
integrity sha512-utLl48nNjSYBoq47RKukZ9fPLEX3nJWThzrujb0ndQQ1jc/gh4RhTRaAqItH9nImnsgGKmLMnyoMBpfGmoop+w==
"@docusaurus/tsconfig@^3.7.0":
version "3.7.0"
resolved "https://registry.yarnpkg.com/@docusaurus/tsconfig/-/tsconfig-3.7.0.tgz#654dcc524e25b8809af0f1b0b42485c18c047ab5"
integrity sha512-vRsyj3yUZCjscgfgcFYjIsTcAru/4h4YH2/XAE8Rs7wWdnng98PgWKvP5ovVc4rmRpRg2WChVW0uOy2xHDvDBQ==
"@docusaurus/types@3.7.0":
version "3.7.0"
@@ -3609,7 +3616,7 @@
dependencies:
"@types/istanbul-lib-report" "*"
"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
@@ -4055,11 +4062,16 @@ acorn-walk@^8.0.0:
dependencies:
acorn "^8.11.0"
acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.8.2, acorn@^8.9.0:
acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.8.2:
version "8.14.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb"
integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==
acorn@^8.9.0:
version "8.14.0"
resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz"
integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
address@^1.0.1, address@^1.1.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e"
@@ -4179,10 +4191,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@^5.25.1:
version "5.25.1"
resolved "https://registry.yarnpkg.com/antd/-/antd-5.25.1.tgz#859b419a18d113492304ccd66c29074a71902241"
integrity sha512-4KC7KuPCjr0z3Vuw9DsF+ceqJaPLbuUI3lOX1sY8ix25ceamp+P8yxOmk3Y2JHCD2ZAhq+5IQ/DTJRN2adWYKQ==
antd@^5.24.5:
version "5.24.5"
resolved "https://registry.yarnpkg.com/antd/-/antd-5.24.5.tgz#b0976a113163888d1477f9e666c3c23352b098e9"
integrity sha512-1lAv/G+9ewQanyoAo3JumQmIlVxwo5QwWGb6QCHYc40Cq0NxC/EzITcjsgq1PSaTUpLkKq8A2l7Fjtu47vqQBg==
dependencies:
"@ant-design/colors" "^7.2.0"
"@ant-design/cssinjs" "^1.23.0"
@@ -4199,37 +4211,37 @@ antd@^5.25.1:
classnames "^2.5.1"
copy-to-clipboard "^3.3.3"
dayjs "^1.11.11"
rc-cascader "~3.34.0"
rc-cascader "~3.33.1"
rc-checkbox "~3.5.0"
rc-collapse "~3.9.0"
rc-dialog "~9.6.0"
rc-drawer "~7.2.0"
rc-dropdown "~4.2.1"
rc-field-form "~2.7.0"
rc-image "~7.12.0"
rc-input "~1.8.0"
rc-input-number "~9.5.0"
rc-mentions "~2.20.0"
rc-image "~7.11.1"
rc-input "~1.7.3"
rc-input-number "~9.4.0"
rc-mentions "~2.19.1"
rc-menu "~9.16.1"
rc-motion "^2.9.5"
rc-notification "~5.6.4"
rc-notification "~5.6.3"
rc-pagination "~5.1.0"
rc-picker "~4.11.3"
rc-progress "~4.0.0"
rc-rate "~2.13.1"
rc-resize-observer "^1.4.3"
rc-segmented "~2.7.0"
rc-select "~14.16.7"
rc-select "~14.16.6"
rc-slider "~11.1.8"
rc-steps "~6.0.1"
rc-switch "~4.1.0"
rc-table "~7.50.4"
rc-tabs "~15.6.1"
rc-textarea "~1.10.0"
rc-tabs "~15.5.1"
rc-textarea "~1.9.0"
rc-tooltip "~6.4.0"
rc-tree "~5.13.1"
rc-tree-select "~5.27.0"
rc-upload "~4.9.0"
rc-upload "~4.8.1"
rc-util "^5.44.4"
scroll-into-view-if-needed "^3.1.0"
throttle-debounce "^5.0.2"
@@ -4646,7 +4658,7 @@ call-bind@^1.0.7, call-bind@^1.0.8:
get-intrinsic "^1.2.4"
set-function-length "^1.2.2"
call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4:
call-bound@^1.0.2, call-bound@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a"
integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
@@ -5667,7 +5679,12 @@ data-view-byte-offset@^1.0.1:
es-errors "^1.3.0"
is-data-view "^1.0.1"
dayjs@^1.11.11, dayjs@^1.11.13:
dayjs@^1.11.11:
version "1.11.12"
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz"
integrity sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==
dayjs@^1.11.13:
version "1.11.13"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
@@ -6247,10 +6264,10 @@ escape-string-regexp@^5.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
eslint-config-prettier@^10.1.5:
version "10.1.5"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz#00c18d7225043b6fbce6a665697377998d453782"
integrity sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==
eslint-config-prettier@^10.1.1:
version "10.1.1"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz#cf0ff6e5c4e7e15f129f1f1ce2a5ecba92dec132"
integrity sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==
eslint-plugin-prettier@^4.0.0:
version "4.2.1"
@@ -6259,10 +6276,10 @@ eslint-plugin-prettier@^4.0.0:
dependencies:
prettier-linter-helpers "^1.0.0"
eslint-plugin-react@^7.37.5:
version "7.37.5"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065"
integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==
eslint-plugin-react@^7.0.0:
version "7.37.4"
resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz"
integrity sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==
dependencies:
array-includes "^3.1.8"
array.prototype.findlast "^1.2.5"
@@ -6274,7 +6291,7 @@ eslint-plugin-react@^7.37.5:
hasown "^2.0.2"
jsx-ast-utils "^2.4.1 || ^3.0.0"
minimatch "^3.1.2"
object.entries "^1.1.9"
object.entries "^1.1.8"
object.fromentries "^2.0.8"
object.values "^1.2.1"
prop-types "^15.8.1"
@@ -8211,15 +8228,15 @@ layout-base@^2.0.0:
resolved "https://registry.yarnpkg.com/layout-base/-/layout-base-2.0.1.tgz#d0337913586c90f9c2c075292069f5c2da5dd285"
integrity sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==
less-loader@^12.3.0:
version "12.3.0"
resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-12.3.0.tgz#d4a00361568be86a97da3df4f16954b0d4c15340"
integrity sha512-0M6+uYulvYIWs52y0LqN4+QM9TqWAohYSNTo4htE8Z7Cn3G/qQMEmktfHmyJT23k+20kU9zHH2wrfFXkxNLtVw==
less-loader@^11.0.0:
version "11.1.4"
resolved "https://registry.npmjs.org/less-loader/-/less-loader-11.1.4.tgz"
integrity sha512-6/GrYaB6QcW6Vj+/9ZPgKKs6G10YZai/l/eJ4SLwbzqNTBsAqt5hSLVF47TgsiBxV1P6eAU0GYRH3YRuQU9V3A==
less@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/less/-/less-4.3.0.tgz#ef0cfc260a9ca8079ed8d0e3512bda8a12c82f2a"
integrity sha512-X9RyH9fvemArzfdP8Pi3irr7lor2Ok4rOttDXBhlwDg+wKQsXOXgHWduAJE1EsF7JJx0w0bcO6BC6tCKKYnXKA==
less@^4.2.2:
version "4.2.2"
resolved "https://registry.npmjs.org/less/-/less-4.2.2.tgz"
integrity sha512-tkuLHQlvWUTeQ3doAqnHbNn8T6WX1KA8yvbKG9x4VtKtIjHsVKQZCH11zRgAfbDAXC2UNIg/K9BYAAcEzUIrNg==
dependencies:
copy-anything "^2.0.1"
parse-node-version "^1.0.1"
@@ -9419,15 +9436,14 @@ object.assign@^4.1.0, object.assign@^4.1.4, object.assign@^4.1.7:
has-symbols "^1.1.0"
object-keys "^1.1.1"
object.entries@^1.1.9:
version "1.1.9"
resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.9.tgz#e4770a6a1444afb61bd39f984018b5bede25f8b3"
integrity sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==
object.entries@^1.1.8:
version "1.1.8"
resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz"
integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==
dependencies:
call-bind "^1.0.8"
call-bound "^1.0.4"
call-bind "^1.0.7"
define-properties "^1.2.1"
es-object-atoms "^1.1.1"
es-object-atoms "^1.0.0"
object.fromentries@^2.0.8:
version "2.0.8"
@@ -10605,10 +10621,10 @@ raw-body@2.5.2:
iconv-lite "0.4.24"
unpipe "1.0.0"
rc-cascader@~3.34.0:
version "3.34.0"
resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-3.34.0.tgz#56f936ab6b1229bab7d558701ce9b9e96536582c"
integrity sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==
rc-cascader@~3.33.1:
version "3.33.1"
resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-3.33.1.tgz#19e01462ef5ef51b723c1f562c7b9cde4691e7ee"
integrity sha512-Kyl4EJ7ZfCBuidmZVieegcbFw0RcU5bHHSbtEdmuLYd0fYHCAiYKZ6zon7fWAVyC6rWWOOib0XKdTSf7ElC9rg==
dependencies:
"@babel/runtime" "^7.25.7"
classnames "^2.3.1"
@@ -10676,10 +10692,10 @@ rc-field-form@~2.7.0:
"@rc-component/async-validator" "^5.0.3"
rc-util "^5.32.2"
rc-image@~7.12.0:
version "7.12.0"
resolved "https://registry.yarnpkg.com/rc-image/-/rc-image-7.12.0.tgz#95e9314701e668217d113c1f29b4f01ac025cafe"
integrity sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==
rc-image@~7.11.1:
version "7.11.1"
resolved "https://registry.yarnpkg.com/rc-image/-/rc-image-7.11.1.tgz#3ab290708dc053d3681de94186522e4e594f6772"
integrity sha512-XuoWx4KUXg7hNy5mRTy1i8c8p3K8boWg6UajbHpDXS5AlRVucNfTi5YxTtPBTBzegxAZpvuLfh3emXFt6ybUdA==
dependencies:
"@babel/runtime" "^7.11.2"
"@rc-component/portal" "^1.0.2"
@@ -10688,37 +10704,37 @@ rc-image@~7.12.0:
rc-motion "^2.6.2"
rc-util "^5.34.1"
rc-input-number@~9.5.0:
version "9.5.0"
resolved "https://registry.yarnpkg.com/rc-input-number/-/rc-input-number-9.5.0.tgz#b47963d0f2cbd85ab2f1badfdc089a904c073f38"
integrity sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==
rc-input-number@~9.4.0:
version "9.4.0"
resolved "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.4.0.tgz"
integrity sha512-Tiy4DcXcFXAf9wDhN8aUAyMeCLHJUHA/VA/t7Hj8ZEx5ETvxG7MArDOSE6psbiSCo+vJPm4E3fGN710ITVn6GA==
dependencies:
"@babel/runtime" "^7.10.1"
"@rc-component/mini-decimal" "^1.0.1"
classnames "^2.2.5"
rc-input "~1.8.0"
rc-input "~1.7.1"
rc-util "^5.40.1"
rc-input@~1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/rc-input/-/rc-input-1.8.0.tgz#d2f4404befebf2fbdc28390d5494c302f74ae974"
integrity sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==
rc-input@~1.7.1, rc-input@~1.7.3:
version "1.7.3"
resolved "https://registry.yarnpkg.com/rc-input/-/rc-input-1.7.3.tgz#cb334a17b93ce985bceb243b4c111a5ed641e0e3"
integrity sha512-A5w4egJq8+4JzlQ55FfQjDnPvOaAbzwC3VLOAdOytyek3TboSOP9qxN+Gifup+shVXfvecBLBbWBpWxmk02SWQ==
dependencies:
"@babel/runtime" "^7.11.1"
classnames "^2.2.1"
rc-util "^5.18.1"
rc-mentions@~2.20.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/rc-mentions/-/rc-mentions-2.20.0.tgz#3bbeac0352b02e0ce3e1244adb48701bb6903bf7"
integrity sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==
rc-mentions@~2.19.1:
version "2.19.1"
resolved "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.19.1.tgz"
integrity sha512-KK3bAc/bPFI993J3necmaMXD2reZTzytZdlTvkeBbp50IGH1BDPDvxLdHDUrpQx2b2TGaVJsn+86BvYa03kGqA==
dependencies:
"@babel/runtime" "^7.22.5"
"@rc-component/trigger" "^2.0.0"
classnames "^2.2.6"
rc-input "~1.8.0"
rc-input "~1.7.1"
rc-menu "~9.16.0"
rc-textarea "~1.10.0"
rc-textarea "~1.9.0"
rc-util "^5.34.1"
rc-menu@~9.16.0, rc-menu@~9.16.1:
@@ -10742,10 +10758,10 @@ rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motio
classnames "^2.2.1"
rc-util "^5.44.0"
rc-notification@~5.6.4:
version "5.6.4"
resolved "https://registry.yarnpkg.com/rc-notification/-/rc-notification-5.6.4.tgz#ea89c39c13cd517fdfd97fe63f03376fabb78544"
integrity sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==
rc-notification@~5.6.3:
version "5.6.3"
resolved "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.3.tgz"
integrity sha512-42szwnn8VYQoT6GnjO00i1iwqV9D1TTMvxObWsuLwgl0TsOokzhkYiufdtQBsJMFjJravS1hfDKVMHLKLcPE4g==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "2.x"
@@ -10821,10 +10837,10 @@ rc-segmented@~2.7.0:
rc-motion "^2.4.4"
rc-util "^5.17.0"
rc-select@~14.16.2, rc-select@~14.16.7:
version "14.16.8"
resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.16.8.tgz#78e6782f1ccc1f03d9003bc3effa4ed609d29a97"
integrity sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==
rc-select@~14.16.2, rc-select@~14.16.6:
version "14.16.6"
resolved "https://registry.npmjs.org/rc-select/-/rc-select-14.16.6.tgz"
integrity sha512-YPMtRPqfZWOm2XGTbx5/YVr1HT0vn//8QS77At0Gjb3Lv+Lbut0IORJPKLWu1hQ3u4GsA0SrDzs7nI8JG7Zmyg==
dependencies:
"@babel/runtime" "^7.10.1"
"@rc-component/trigger" "^2.1.1"
@@ -10873,10 +10889,10 @@ rc-table@~7.50.4:
rc-util "^5.44.3"
rc-virtual-list "^3.14.2"
rc-tabs@~15.6.1:
version "15.6.1"
resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-15.6.1.tgz#f0b6c65384dfa09a64eb539e86a0667c7a650708"
integrity sha512-/HzDV1VqOsUWyuC0c6AkxVYFjvx9+rFPKZ32ejxX0Uc7QCzcEjTA9/xMgv4HemPKwzBNX8KhGVbbumDjnj92aA==
rc-tabs@~15.5.1:
version "15.5.1"
resolved "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.5.1.tgz"
integrity sha512-yiWivLAjEo5d1v2xlseB2dQocsOhkoVSfo1krS8v8r+02K+TBUjSjXIf7dgyVSxp6wRIPv5pMi5hanNUlQMgUA==
dependencies:
"@babel/runtime" "^7.11.2"
classnames "2.x"
@@ -10886,14 +10902,14 @@ rc-tabs@~15.6.1:
rc-resize-observer "^1.0.0"
rc-util "^5.34.1"
rc-textarea@~1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-1.10.0.tgz#f8f962ef83be0b8e35db97cf03dbfb86ddd9c46c"
integrity sha512-ai9IkanNuyBS4x6sOL8qu/Ld40e6cEs6pgk93R+XLYg0mDSjNBGey6/ZpDs5+gNLD7urQ14po3V6Ck2dJLt9SA==
rc-textarea@~1.9.0:
version "1.9.0"
resolved "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.9.0.tgz"
integrity sha512-dQW/Bc/MriPBTugj2Kx9PMS5eXCCGn2cxoIaichjbNvOiARlaHdI99j4DTxLl/V8+PIfW06uFy7kjfUIDDKyxQ==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "^2.2.1"
rc-input "~1.8.0"
rc-input "~1.7.1"
rc-resize-observer "^1.0.0"
rc-util "^5.27.0"
@@ -10929,10 +10945,10 @@ rc-tree@~5.13.0, rc-tree@~5.13.1:
rc-util "^5.16.1"
rc-virtual-list "^3.5.1"
rc-upload@~4.9.0:
version "4.9.0"
resolved "https://registry.yarnpkg.com/rc-upload/-/rc-upload-4.9.0.tgz#911963ab5a0b538c743765371c05e2de9e3f5436"
integrity sha512-pAzlPnyiFn1GCtEybEG2m9nXNzQyWXqWV2xFYCmDxjN9HzyjS5Pz2F+pbNdYw8mMJsixLEKLG0wVy9vOGxJMJA==
rc-upload@~4.8.1:
version "4.8.1"
resolved "https://registry.npmjs.org/rc-upload/-/rc-upload-4.8.1.tgz"
integrity sha512-toEAhwl4hjLAI1u8/CgKWt30BR06ulPa4iGQSMvSXoHzO88gPCslxqV/mnn4gJU7PDoltGIC9Eh+wkeudqgHyw==
dependencies:
"@babel/runtime" "^7.18.3"
classnames "^2.2.5"
@@ -11527,7 +11543,7 @@ resolve-pathname@^3.0.0:
resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
resolve@^1.1.6, resolve@^1.12.0, resolve@^1.14.2:
resolve@^1.1.6, resolve@^1.14.2:
version "1.22.10"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39"
integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==
@@ -11536,6 +11552,15 @@ resolve@^1.1.6, resolve@^1.12.0, resolve@^1.14.2:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
resolve@^1.12.0:
version "1.22.8"
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
dependencies:
is-core-module "^2.13.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
resolve@^2.0.0-next.5:
version "2.0.0-next.5"
resolved "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz"
@@ -11684,10 +11709,10 @@ schema-utils@^3.0.0:
ajv "^6.12.5"
ajv-keywords "^3.5.2"
schema-utils@^4.0.0, schema-utils@^4.0.1, schema-utils@^4.3.0, schema-utils@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.2.tgz#0c10878bf4a73fd2b1dfd14b9462b26788c806ae"
integrity sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==
schema-utils@^4.0.0, schema-utils@^4.0.1, schema-utils@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.0.tgz#3b669f04f71ff2dfb5aba7ce2d5a9d79b35622c0"
integrity sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==
dependencies:
"@types/json-schema" "^7.0.9"
ajv "^8.9.0"
@@ -12334,10 +12359,10 @@ swagger-client@^3.34.4:
ramda "^0.30.1"
ramda-adjunct "^5.1.0"
swagger-ui-react@^5.21.0:
version "5.21.0"
resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-5.21.0.tgz#a9b22a24247c23eafb39b776bee5a4d5b56c57fe"
integrity sha512-lS5paITM1kkcBb/BTTSMHKelh8elHfcuUP4T3R3mO80tDR0AYJL2HR5UdQD6nV1LwdvekzRM8gKjJA6hVayi0A==
swagger-ui-react@^5.20.2:
version "5.20.2"
resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-5.20.2.tgz#eb9f8de2e6c916661d8033d43119f6e105ab87da"
integrity sha512-6ifaFjT02yBv1kjEivIMWxQpI7r8O7D/oA8u1JiwhTkom0dOk85lTExao5Dj5ztS6dBg6i1zm+ILhH94fF9g8Q==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@scarf/scarf" "=1.4.0"
@@ -12515,11 +12540,16 @@ tslib@^1.8.1:
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.3, tslib@^2.3.0, tslib@^2.6.0:
tslib@^2.0.3, tslib@^2.6.0:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
tslib@^2.3.0:
version "2.6.2"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz"
@@ -12621,10 +12651,10 @@ types-ramda@^0.30.0:
dependencies:
ts-toolbelt "^9.6.0"
typescript@~5.8.3:
version "5.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==
typescript@~5.8.2:
version "5.8.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4"
integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==
ufo@^1.5.4:
version "1.6.1"
@@ -13033,14 +13063,13 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.88.1, webpack@^5.95.0, webpack@^5.99.9:
version "5.99.9"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.99.9.tgz#d7de799ec17d0cce3c83b70744b4aedb537d8247"
integrity sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==
webpack@^5.88.1:
version "5.99.5"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.99.5.tgz#86e3b3a5a03377ea5da271c929934003f5ac5dd8"
integrity sha512-q+vHBa6H9qwBLUlHL4Y7L0L1/LlyBKZtS9FHNCQmtayxjI5RKC9yD8gpvLeqGv5lCQp1Re04yi0MF40pf30Pvg==
dependencies:
"@types/eslint-scope" "^3.7.7"
"@types/estree" "^1.0.6"
"@types/json-schema" "^7.0.15"
"@webassemblyjs/ast" "^1.14.1"
"@webassemblyjs/wasm-edit" "^1.14.1"
"@webassemblyjs/wasm-parser" "^1.14.1"
@@ -13057,7 +13086,36 @@ webpack@^5.88.1, webpack@^5.95.0, webpack@^5.99.9:
loader-runner "^4.2.0"
mime-types "^2.1.27"
neo-async "^2.6.2"
schema-utils "^4.3.2"
schema-utils "^4.3.0"
tapable "^2.1.1"
terser-webpack-plugin "^5.3.11"
watchpack "^2.4.1"
webpack-sources "^3.2.3"
webpack@^5.95.0, webpack@^5.98.0:
version "5.98.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.98.0.tgz#44ae19a8f2ba97537978246072fb89d10d1fbd17"
integrity sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==
dependencies:
"@types/eslint-scope" "^3.7.7"
"@types/estree" "^1.0.6"
"@webassemblyjs/ast" "^1.14.1"
"@webassemblyjs/wasm-edit" "^1.14.1"
"@webassemblyjs/wasm-parser" "^1.14.1"
acorn "^8.14.0"
browserslist "^4.24.0"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.17.1"
es-module-lexer "^1.2.1"
eslint-scope "5.1.1"
events "^3.2.0"
glob-to-regexp "^0.4.1"
graceful-fs "^4.2.11"
json-parse-even-better-errors "^2.3.1"
loader-runner "^4.2.0"
mime-types "^2.1.27"
neo-async "^2.6.2"
schema-utils "^4.3.0"
tapable "^2.1.1"
terser-webpack-plugin "^5.3.11"
watchpack "^2.4.1"

View File

@@ -32,7 +32,6 @@ authors = [
classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"backoff>=1.8.0",
@@ -45,7 +44,7 @@ dependencies = [
"cryptography>=42.0.4, <45.0.0",
"deprecation>=2.1.0, <2.2.0",
"flask>=2.2.5, <3.0.0",
"flask-appbuilder>=4.7.0, <5.0.0",
"flask-appbuilder>=4.6.1, <5.0.0",
"flask-caching>=2.1.0, <3",
"flask-compress>=1.13, <2.0",
"flask-talisman>=1.0.0, <2.0",
@@ -67,7 +66,7 @@ dependencies = [
"markdown>=3.0",
"msgpack>=1.0.0, <1.1",
"nh3>=0.2.11, <0.3",
"numpy>1.23.5, <2.3",
"numpy>1.23.5, <2",
"packaging",
# --------------------------
# pandas and related (wanting pandas[performance] without numba as it's 100+MB and not needed)
@@ -82,7 +81,7 @@ dependencies = [
"python-dateutil",
"python-dotenv", # optional dependencies for Flask but required for Superset, see https://flask.palletsprojects.com/en/stable/installation/#optional-dependencies
"python-geohash",
"pyarrow>=18.1.0, <19",
"pyarrow>=14.0.1, <15",
"pyyaml>=6.0.0, <7.0.0",
"PyJWT>=2.4.0, <3.0",
"redis>=4.6.0, <5.0",
@@ -94,8 +93,8 @@ dependencies = [
"sqlalchemy>=1.4, <2",
"sqlalchemy-utils>=0.38.3, <0.39",
"sqlglot>=26.1.3, <27",
# newer pandas needs 0.9+
"tabulate>=0.9.0, <1.0",
"sqlparse>=0.5.0",
"tabulate>=0.8.9, <0.9",
"typing-extensions>=4, <5",
"waitress; sys_platform == 'win32'",
"wtforms>=2.3.3, <4",
@@ -167,7 +166,6 @@ prophet = ["prophet>=1.1.5, <2"]
redshift = ["sqlalchemy-redshift>=0.8.1, <0.9"]
rockset = ["rockset-sqlalchemy>=0.0.1, <1"]
shillelagh = ["shillelagh[all]>=1.2.18, <2"]
singlestore = ["sqlalchemy-singlestoredb>=1.1.1, <2"]
snowflake = ["snowflake-sqlalchemy>=1.2.4, <2"]
spark = [
"pyhive[hive]>=0.6.5;python_version<'3.11'",
@@ -199,7 +197,6 @@ development = [
"psutil",
"pyfakefs",
"pyinstrument>=4.0.2,<5",
"pylint",
"pytest<8.0.0", # hairy issue with pytest >=8 where current_app proxies are not set in time
"pytest-cov",
"pytest-mock",
@@ -219,7 +216,7 @@ combine_as_imports = true
include_trailing_comma = true
line_length = 88
known_first_party = "superset"
known_third_party = "alembic, apispec, backoff, celery, click, colorama, cron_descriptor, croniter, cryptography, dateutil, deprecation, flask, flask_appbuilder, flask_babel, flask_caching, flask_compress, flask_jwt_extended, flask_login, flask_migrate, flask_sqlalchemy, flask_talisman, flask_testing, flask_wtf, freezegun, geohash, geopy, holidays, humanize, isodate, jinja2, jwt, markdown, markupsafe, marshmallow, msgpack, nh3, numpy, pandas, parameterized, parsedatetime, pgsanity, polyline, prison, progress, pyarrow, sqlalchemy_bigquery, pyhive, pyparsing, pytest, pytest_mock, pytz, redis, requests, selenium, setuptools, shillelagh, simplejson, slack, sqlalchemy, sqlalchemy_utils, typing_extensions, urllib3, werkzeug, wtforms, wtforms_json, yaml"
known_third_party = "alembic, apispec, backoff, celery, click, colorama, cron_descriptor, croniter, cryptography, dateutil, deprecation, flask, flask_appbuilder, flask_babel, flask_caching, flask_compress, flask_jwt_extended, flask_login, flask_migrate, flask_sqlalchemy, flask_talisman, flask_testing, flask_wtf, freezegun, geohash, geopy, holidays, humanize, isodate, jinja2, jwt, markdown, markupsafe, marshmallow, msgpack, nh3, numpy, pandas, parameterized, parsedatetime, pgsanity, polyline, prison, progress, pyarrow, sqlalchemy_bigquery, pyhive, pyparsing, pytest, pytest_mock, pytz, redis, requests, selenium, setuptools, shillelagh, simplejson, slack, sqlalchemy, sqlalchemy_utils, sqlparse, typing_extensions, urllib3, werkzeug, wtforms, wtforms_json, yaml"
multi_line_output = 3
order_by_type = false
@@ -243,12 +240,6 @@ disallow_untyped_calls = false
disallow_untyped_defs = false
disable_error_code = "annotation-unchecked"
# TODO: remove this once cryptography is fixed, introduced in cryptography 44.0.3
[[tool.mypy.overrides]]
module = "cryptography.*"
ignore_errors = true
follow_imports = "skip"
[tool.ruff]
# Exclude a variety of commonly ignored directories.
exclude = [
@@ -281,6 +272,7 @@ exclude = [
"venv",
]
# Same as Black.
line-length = 88
indent-width = 4
@@ -375,21 +367,17 @@ docstring-code-line-length = "dynamic"
requirement_txt_file = "requirements/base.txt"
authorized_licenses = [
"academic free license (afl)",
"any-osi",
"apache license 2.0",
"apache software",
"apache software, bsd",
"bsd",
"bsd-3-clause",
"isc license (iscl)",
"isc license",
"mit",
"mozilla public license 2.0 (mpl 2.0)",
"osi approved",
"osi approved",
"psf-2.0",
"python software foundation",
"simplified bsd",
"the unlicense (unlicense)",
"the unlicense",
]

View File

@@ -1,13 +0,0 @@
## Python dependency logic
In this folder, the `.in` files, in conjunction with the `../pyproject.toml` file (in the root of the repo) are used to generate the pinned requirements as `.txt` files.
To alter the pinned dependency, you can edit/alter the `.in` and `pyproject.toml` files, and then run the following command:
```bash
./scripts/uv-pip-compile.sh
```
This will generate the pinned requirements in the `.txt` files, which will be used in our CI/CD pipelines and in the Docker images.
We recommend to everyone in the community to use the pinned requirements in their local development environments, to ensure consistency across different environments, though we don't force requirements as part of our python package semantics to allow flexibility for users to install different versions of the dependencies if they wish.

View File

@@ -33,6 +33,3 @@ apispec>=6.0.0,<6.7.0
# https://marshmallow-sqlalchemy.readthedocs.io/en/latest/changelog.html#id3
# Opened this issue https://github.com/marshmallow-code/marshmallow-sqlalchemy/issues/665
marshmallow-sqlalchemy>=1.3.0,<1.4.1
# needed for python 3.12 support
openapi-schema-validator>=0.6.3

View File

@@ -1,6 +1,6 @@
# This file was autogenerated by uv via the following command:
# uv pip compile pyproject.toml requirements/base.in -o requirements/base.txt
alembic==1.15.2
alembic==1.15.1
# via flask-migrate
amqp==5.3.1
# via kombu
@@ -8,7 +8,7 @@ apispec==6.6.1
# via
# -r requirements/base.in
# flask-appbuilder
apsw==3.50.1.0
apsw==3.49.1.0
# via shillelagh
async-timeout==4.0.3
# via
@@ -32,7 +32,7 @@ billiard==4.2.1
# via celery
blinker==1.9.0
# via flask
bottleneck==1.5.0
bottleneck==1.4.2
# via apache-superset (pyproject.toml)
brotli==1.1.0
# via flask-compress
@@ -42,11 +42,11 @@ cachelib==0.13.0
# flask-session
cachetools==5.5.2
# via google-auth
cattrs==25.1.1
cattrs==24.1.2
# via requests-cache
celery==5.5.2
# via apache-superset (pyproject.toml)
certifi==2025.6.15
certifi==2025.1.31
# via
# requests
# selenium
@@ -54,9 +54,9 @@ cffi==1.17.1
# via
# cryptography
# pynacl
charset-normalizer==3.4.2
charset-normalizer==3.4.1
# via requests
click==8.2.1
click==8.1.8
# via
# apache-superset (pyproject.toml)
# celery
@@ -82,7 +82,7 @@ cron-descriptor==1.4.5
# via apache-superset (pyproject.toml)
croniter==6.0.0
# via apache-superset (pyproject.toml)
cryptography==44.0.3
cryptography==44.0.2
# via
# apache-superset (pyproject.toml)
# paramiko
@@ -99,7 +99,7 @@ email-validator==2.2.0
# via flask-appbuilder
et-xmlfile==2.0.0
# via openpyxl
exceptiongroup==1.3.0
exceptiongroup==1.2.2
# via
# cattrs
# trio
@@ -118,7 +118,7 @@ flask==2.3.3
# flask-session
# flask-sqlalchemy
# flask-wtf
flask-appbuilder==4.7.0
flask-appbuilder==4.6.1
# via apache-superset (pyproject.toml)
flask-babel==2.0.0
# via flask-appbuilder
@@ -152,7 +152,7 @@ geographiclib==2.0
# via geopy
geopy==2.4.1
# via apache-superset (pyproject.toml)
google-auth==2.40.3
google-auth==2.38.0
# via shillelagh
greenlet==3.1.1
# via
@@ -161,20 +161,19 @@ greenlet==3.1.1
# sqlalchemy
gunicorn==23.0.0
# via apache-superset (pyproject.toml)
h11==0.16.0
h11==0.14.0
# via wsproto
hashids==1.3.1
# via apache-superset (pyproject.toml)
holidays==0.25
# via apache-superset (pyproject.toml)
humanize==4.12.3
humanize==4.12.2
# via apache-superset (pyproject.toml)
idna==3.10
# via
# email-validator
# requests
# trio
# url-normalize
importlib-metadata==8.7.0
# via apache-superset (pyproject.toml)
isodate==0.7.2
@@ -190,13 +189,9 @@ jinja2==3.1.6
jsonpath-ng==1.7.0
# via apache-superset (pyproject.toml)
jsonschema==4.23.0
# via
# flask-appbuilder
# openapi-schema-validator
jsonschema-specifications==2025.4.1
# via
# jsonschema
# openapi-schema-validator
# via flask-appbuilder
jsonschema-specifications==2024.10.1
# via jsonschema
kombu==5.5.3
# via celery
korean-lunar-calendar==0.3.1
@@ -207,7 +202,7 @@ mako==1.3.10
# via
# apache-superset (pyproject.toml)
# alembic
markdown==3.8
markdown==3.7
# via apache-superset (pyproject.toml)
markdown-it-py==3.0.0
# via rich
@@ -241,18 +236,15 @@ numpy==1.26.4
# bottleneck
# numexpr
# pandas
# pyarrow
odfpy==1.4.1
# via pandas
openapi-schema-validator==0.6.3
# via -r requirements/base.in
openpyxl==3.1.5
# via pandas
ordered-set==4.1.0
# via flask-limiter
outcome==1.3.0.post0
# via
# trio
# trio-websocket
# via trio
packaging==25.0
# via
# apache-superset (pyproject.toml)
@@ -272,7 +264,7 @@ parsedatetime==2.6
# via apache-superset (pyproject.toml)
pgsanity==0.2.9
# via apache-superset (pyproject.toml)
platformdirs==4.3.8
platformdirs==4.3.7
# via requests-cache
ply==3.11
# via jsonpath-ng
@@ -282,13 +274,13 @@ prison==0.2.1
# via flask-appbuilder
prompt-toolkit==3.0.51
# via click-repl
pyarrow==18.1.0
pyarrow==14.0.2
# via apache-superset (pyproject.toml)
pyasn1==0.6.1
# via
# pyasn1-modules
# rsa
pyasn1-modules==0.4.2
pyasn1-modules==0.4.1
# via google-auth
pycparser==2.22
# via cffi
@@ -301,7 +293,7 @@ pyjwt==2.10.1
# flask-jwt-extended
pynacl==1.5.0
# via paramiko
pyopenssl==25.1.0
pyopenssl==25.0.0
# via shillelagh
pyparsing==3.2.3
# via apache-superset (pyproject.toml)
@@ -337,23 +329,21 @@ referencing==0.36.2
# via
# jsonschema
# jsonschema-specifications
requests==2.32.4
requests==2.32.3
# via
# requests-cache
# shillelagh
requests-cache==1.2.1
# via shillelagh
rfc3339-validator==0.1.4
# via openapi-schema-validator
rich==13.9.4
# via flask-limiter
rpds-py==0.25.0
rpds-py==0.23.1
# via
# jsonschema
# referencing
rsa==4.9.1
rsa==4.9
# via google-auth
selenium==4.32.0
selenium==4.27.1
# via apache-superset (pyproject.toml)
shillelagh==1.3.5
# via apache-superset (pyproject.toml)
@@ -363,7 +353,7 @@ six==1.17.0
# via
# prison
# python-dateutil
# rfc3339-validator
# url-normalize
# wtforms-json
slack-sdk==3.35.0
# via apache-superset (pyproject.toml)
@@ -384,24 +374,25 @@ sqlalchemy-utils==0.38.3
# via
# apache-superset (pyproject.toml)
# flask-appbuilder
sqlglot==26.28.1
sqlglot==26.16.2
# via apache-superset (pyproject.toml)
sqlparse==0.5.3
# via apache-superset (pyproject.toml)
sshtunnel==0.4.0
# via apache-superset (pyproject.toml)
tabulate==0.9.0
tabulate==0.8.10
# via apache-superset (pyproject.toml)
trio==0.30.0
trio==0.28.0
# via
# selenium
# trio-websocket
trio-websocket==0.12.2
trio-websocket==0.11.1
# via selenium
typing-extensions==4.14.0
typing-extensions==4.12.2
# via
# apache-superset (pyproject.toml)
# alembic
# cattrs
# exceptiongroup
# limits
# pyopenssl
# referencing
@@ -412,7 +403,7 @@ tzdata==2025.2
# via
# kombu
# pandas
url-normalize==2.2.1
url-normalize==1.4.3
# via requests-cache
urllib3==1.26.20
# via
@@ -454,7 +445,7 @@ xlsxwriter==3.0.9
# via
# apache-superset (pyproject.toml)
# pandas
zipp==3.23.0
zipp==3.21.0
# via importlib-metadata
zstandard==0.23.0
# via flask-compress

View File

@@ -2,7 +2,7 @@
# uv pip compile requirements/development.in -c requirements/base.txt -o requirements/development.txt
-e .
# via -r requirements/development.in
alembic==1.15.2
alembic==1.15.1
# via
# -c requirements/base.txt
# flask-migrate
@@ -14,12 +14,10 @@ apispec==6.6.1
# via
# -c requirements/base.txt
# flask-appbuilder
apsw==3.50.1.0
apsw==3.49.1.0
# via
# -c requirements/base.txt
# shillelagh
astroid==3.3.10
# via pylint
async-timeout==4.0.3
# via
# -c requirements/base.txt
@@ -53,7 +51,7 @@ blinker==1.9.0
# via
# -c requirements/base.txt
# flask
bottleneck==1.5.0
bottleneck==1.4.2
# via
# -c requirements/base.txt
# apache-superset
@@ -70,7 +68,7 @@ cachetools==5.5.2
# via
# -c requirements/base.txt
# google-auth
cattrs==25.1.1
cattrs==24.1.2
# via
# -c requirements/base.txt
# requests-cache
@@ -78,7 +76,7 @@ celery==5.5.2
# via
# -c requirements/base.txt
# apache-superset
certifi==2025.6.15
certifi==2025.1.31
# via
# -c requirements/base.txt
# requests
@@ -90,11 +88,11 @@ cffi==1.17.1
# pynacl
cfgv==3.4.0
# via pre-commit
charset-normalizer==3.4.2
charset-normalizer==3.4.1
# via
# -c requirements/base.txt
# requests
click==8.2.1
click==8.1.8
# via
# -c requirements/base.txt
# apache-superset
@@ -140,7 +138,7 @@ croniter==6.0.0
# via
# -c requirements/base.txt
# apache-superset
cryptography==44.0.3
cryptography==44.0.2
# via
# -c requirements/base.txt
# apache-superset
@@ -162,8 +160,6 @@ deprecation==2.1.0
# via
# -c requirements/base.txt
# apache-superset
dill==0.4.0
# via pylint
distlib==0.3.8
# via virtualenv
dnspython==2.7.0
@@ -180,7 +176,7 @@ et-xmlfile==2.0.0
# via
# -c requirements/base.txt
# openpyxl
exceptiongroup==1.3.0
exceptiongroup==1.2.2
# via
# -c requirements/base.txt
# cattrs
@@ -206,7 +202,7 @@ flask==2.3.3
# flask-sqlalchemy
# flask-testing
# flask-wtf
flask-appbuilder==4.7.0
flask-appbuilder==4.6.1
# via
# -c requirements/base.txt
# apache-superset
@@ -284,7 +280,7 @@ google-api-core==2.23.0
# google-cloud-core
# pandas-gbq
# sqlalchemy-bigquery
google-auth==2.40.3
google-auth==2.38.0
# via
# -c requirements/base.txt
# google-api-core
@@ -334,7 +330,7 @@ gunicorn==23.0.0
# via
# -c requirements/base.txt
# apache-superset
h11==0.16.0
h11==0.14.0
# via
# -c requirements/base.txt
# wsproto
@@ -347,7 +343,7 @@ holidays==0.25
# -c requirements/base.txt
# apache-superset
# prophet
humanize==4.12.3
humanize==4.12.2
# via
# -c requirements/base.txt
# apache-superset
@@ -359,7 +355,6 @@ idna==3.10
# email-validator
# requests
# trio
# url-normalize
importlib-metadata==8.7.0
# via
# -c requirements/base.txt
@@ -372,8 +367,6 @@ isodate==0.7.2
# via
# -c requirements/base.txt
# apache-superset
isort==6.0.1
# via pylint
itsdangerous==2.2.0
# via
# -c requirements/base.txt
@@ -396,7 +389,7 @@ jsonschema==4.23.0
# openapi-spec-validator
jsonschema-path==0.3.4
# via openapi-spec-validator
jsonschema-specifications==2025.4.1
jsonschema-specifications==2024.10.1
# via
# -c requirements/base.txt
# jsonschema
@@ -422,7 +415,7 @@ mako==1.3.10
# -c requirements/base.txt
# alembic
# apache-superset
markdown==3.8
markdown==3.7
# via
# -c requirements/base.txt
# apache-superset
@@ -448,8 +441,6 @@ marshmallow-sqlalchemy==1.4.0
# flask-appbuilder
matplotlib==3.9.0
# via prophet
mccabe==0.7.0
# via pylint
mdurl==0.1.2
# via
# -c requirements/base.txt
@@ -482,6 +473,7 @@ numpy==1.26.4
# pandas
# pandas-gbq
# prophet
# pyarrow
oauthlib==3.2.2
# via requests-oauthlib
odfpy==1.4.1
@@ -489,9 +481,7 @@ odfpy==1.4.1
# -c requirements/base.txt
# pandas
openapi-schema-validator==0.6.3
# via
# -c requirements/base.txt
# openapi-spec-validator
# via openapi-spec-validator
openapi-spec-validator==0.7.1
# via apache-superset
openpyxl==3.1.5
@@ -506,7 +496,6 @@ outcome==1.3.0.post0
# via
# -c requirements/base.txt
# trio
# trio-websocket
packaging==25.0
# via
# -c requirements/base.txt
@@ -554,10 +543,9 @@ pillow==10.3.0
# via
# apache-superset
# matplotlib
platformdirs==4.3.8
platformdirs==4.3.7
# via
# -c requirements/base.txt
# pylint
# requests-cache
# virtualenv
pluggy==1.5.0
@@ -599,7 +587,7 @@ psutil==6.1.0
# via apache-superset
psycopg2-binary==2.9.6
# via apache-superset
pyarrow==18.1.0
pyarrow==14.0.2
# via
# -c requirements/base.txt
# apache-superset
@@ -611,7 +599,7 @@ pyasn1==0.6.1
# pyasn1-modules
# python-ldap
# rsa
pyasn1-modules==0.4.2
pyasn1-modules==0.4.1
# via
# -c requirements/base.txt
# google-auth
@@ -640,13 +628,11 @@ pyjwt==2.10.1
# apache-superset
# flask-appbuilder
# flask-jwt-extended
pylint==3.3.7
# via apache-superset
pynacl==1.5.0
# via
# -c requirements/base.txt
# paramiko
pyopenssl==25.1.0
pyopenssl==25.0.0
# via
# -c requirements/base.txt
# shillelagh
@@ -721,7 +707,7 @@ referencing==0.36.2
# jsonschema
# jsonschema-path
# jsonschema-specifications
requests==2.32.4
requests==2.32.3
# via
# -c requirements/base.txt
# docker
@@ -741,29 +727,27 @@ requests-cache==1.2.1
requests-oauthlib==2.0.0
# via google-auth-oauthlib
rfc3339-validator==0.1.4
# via
# -c requirements/base.txt
# openapi-schema-validator
# via openapi-schema-validator
rich==13.9.4
# via
# -c requirements/base.txt
# flask-limiter
rpds-py==0.25.0
rpds-py==0.23.1
# via
# -c requirements/base.txt
# jsonschema
# referencing
rsa==4.9.1
rsa==4.9
# via
# -c requirements/base.txt
# google-auth
ruff==0.8.0
# via apache-superset
selenium==4.32.0
selenium==4.27.1
# via
# -c requirements/base.txt
# apache-superset
setuptools==80.7.1
setuptools==75.6.0
# via
# nodeenv
# pandas-gbq
@@ -784,6 +768,7 @@ six==1.17.0
# prison
# python-dateutil
# rfc3339-validator
# url-normalize
# wtforms-json
slack-sdk==3.35.0
# via
@@ -815,52 +800,51 @@ sqlalchemy-utils==0.38.3
# -c requirements/base.txt
# apache-superset
# flask-appbuilder
sqlglot==26.28.1
sqlglot==26.16.2
# via
# -c requirements/base.txt
# apache-superset
sqloxide==0.1.51
# via apache-superset
sqlparse==0.5.3
# via
# -c requirements/base.txt
# apache-superset
sshtunnel==0.4.0
# via
# -c requirements/base.txt
# apache-superset
statsd==4.0.1
# via apache-superset
tabulate==0.9.0
tabulate==0.8.10
# via
# -c requirements/base.txt
# apache-superset
tomli==2.2.1
# via
# coverage
# pylint
# pytest
tomlkit==0.13.3
# via pylint
tqdm==4.67.1
# via
# cmdstanpy
# prophet
trino==0.330.0
# via apache-superset
trio==0.30.0
trio==0.28.0
# via
# -c requirements/base.txt
# selenium
# trio-websocket
trio-websocket==0.12.2
trio-websocket==0.11.1
# via
# -c requirements/base.txt
# selenium
typing-extensions==4.14.0
typing-extensions==4.12.2
# via
# -c requirements/base.txt
# alembic
# apache-superset
# astroid
# cattrs
# exceptiongroup
# limits
# pyopenssl
# referencing
@@ -874,7 +858,7 @@ tzdata==2025.2
# pandas
tzlocal==5.2
# via trino
url-normalize==2.2.1
url-normalize==1.4.3
# via
# -c requirements/base.txt
# requests-cache
@@ -936,7 +920,7 @@ xlsxwriter==3.0.9
# -c requirements/base.txt
# apache-superset
# pandas
zipp==3.23.0
zipp==3.21.0
# via
# -c requirements/base.txt
# importlib-metadata

View File

@@ -116,11 +116,8 @@ Example `POST /security/guest_token` payload:
}
```
Alternatively, a guest token can be created directly in your app without interacting with the Superset API.
To do this, you should update the `GUEST_TOKEN_JWT_SECRET`
in the Superset [config.py](https://github.com/apache/superset/blob/master/superset/config.py). Also set the
`GUEST_TOKEN_JWT_AUDIENCE` variable that matches what is set for the `aud` in the JSON payload:
Alternatively, a guest token can be created directly in your app with a json like the following, and then signed
with the secret set in configuration variable `GUEST_TOKEN_JWT_SECRET` (see configuration file config.py)
```
{
"user": {
@@ -142,13 +139,6 @@ in the Superset [config.py](https://github.com/apache/superset/blob/master/super
}
```
In this example, the configuration file includes the following setting:
```python
GUEST_TOKEN_JWT_AUDIENCE="superset"
```
### Sandbox iframe
The Embedded SDK creates an iframe with [sandbox](https://developer.mozilla.org/es/docs/Web/HTML/Element/iframe#sandbox) mode by default

View File

@@ -16,17 +16,23 @@
* specific language governing permissions and limitations
* under the License.
*/
import { ALERT_LIST } from 'cypress/utils/urls';
import { useEffect, useRef } from 'react';
describe('Alert list view', () => {
before(() => {
cy.visit(ALERT_LIST);
});
export function useOpenerRef(active: boolean) {
const openerRef = useRef<HTMLElement | null>(null);
useEffect(() => {
if (active) {
openerRef.current = document.activeElement as HTMLElement;
}
}, [active]);
return openerRef;
}
it('should load alert lists', () => {
cy.getBySel('listview-table').should('be.visible');
cy.getBySel('sort-header').eq(1).contains('Last run');
cy.getBySel('sort-header').eq(2).contains('Name');
cy.getBySel('sort-header').eq(3).contains('Schedule');
cy.getBySel('sort-header').eq(4).contains('Notification method');
cy.getBySel('sort-header').eq(5).contains('Owners');
cy.getBySel('sort-header').eq(6).contains('Last modified');
cy.getBySel('sort-header').eq(7).contains('Active');
// TODO Cypress won't recognize the Actions column
// cy.getBySel('sort-header').eq(9).contains('Actions');
});
});

View File

@@ -16,7 +16,23 @@
* specific language governing permissions and limitations
* under the License.
*/
import { CONTRIBUTION_SUFFIX } from './constants';
import { REPORT_LIST } from 'cypress/utils/urls';
export const getContributionLabel = (metricLabel: string) =>
`${metricLabel}${CONTRIBUTION_SUFFIX}`;
describe('Report list view', () => {
before(() => {
cy.visit(REPORT_LIST);
});
it('should load report lists', () => {
cy.getBySel('listview-table').should('be.visible');
cy.getBySel('sort-header').eq(1).contains('Last run');
cy.getBySel('sort-header').eq(2).contains('Name');
cy.getBySel('sort-header').eq(3).contains('Schedule');
cy.getBySel('sort-header').eq(4).contains('Notification method');
cy.getBySel('sort-header').eq(5).contains('Owners');
cy.getBySel('sort-header').eq(6).contains('Last modified');
cy.getBySel('sort-header').eq(7).contains('Active');
// TODO Cypress won't recognize the Actions column
// cy.getBySel('sort-header').eq(9).contains('Actions');
});
});

View File

@@ -189,10 +189,8 @@ export function interceptFilterState() {
export function setFilter(filter: string, option: string) {
interceptFiltering();
cy.get(`[aria-label^="${filter}"]`).first().click();
cy.get(`.ant-select-item-option[title="${option}"]`).first().click({
force: true,
});
cy.get(`[aria-label="${filter}"]`).first().click();
cy.get(`[aria-label="${filter}"] [title="${option}"]`).click();
cy.wait('@filtering');
}
@@ -348,10 +346,8 @@ export function addParentFilterWithValue(index: number, value: string) {
return cy
.get(nativeFilters.filterConfigurationSections.displayedSection)
.within(() => {
cy.get('input[aria-label^="Limit type"]')
.eq(index)
.click({ force: true });
cy.get('input[aria-label^="Limit type"]')
cy.get('input[aria-label="Limit type"]').eq(index).click({ force: true });
cy.get('input[aria-label="Limit type"]')
.eq(index)
.type(`${value}{enter}`, { delay: 30, force: true });
});

View File

@@ -154,7 +154,7 @@ describe('Test explore links', () => {
// This time around, typing the same dashboard name
// will select the existing one
cy.get('[data-test="save-chart-modal-select-dashboard-form"]')
.find('input[aria-label^="Select a dashboard"]')
.find('input[aria-label="Select a dashboard"]')
.type(`${dashboardTitle}{enter}`, { force: true });
cy.get(`.ant-select-item[label="${dashboardTitle}"]`).click({

View File

@@ -61,10 +61,8 @@ export function interceptExploreGet() {
export function setFilter(filter: string, option: string) {
interceptFiltering();
cy.get(`[aria-label^="${filter}"]`).first().click();
cy.get(`.ant-select-item-option[title="${option}"]`).first().click({
force: true,
});
cy.get(`[aria-label="${filter}"]`).first().click();
cy.get(`[aria-label="${filter}"] [title="${option}"]`).click();
cy.wait('@filtering');
}
@@ -78,18 +76,17 @@ export function saveChartToDashboard(dashboardName: string) {
.should('be.enabled')
.should('not.be.disabled')
.click();
cy.getBySelLike('chart-modal')
.should('be.visible')
.within(() => {
cy.get('[data-test="save-chart-modal-select-dashboard-form"]')
.first()
.click();
cy.get('.ant-select-selection-search-input').type(dashboardName, {
force: true,
});
cy.get(`.ant-select-item-option[title="${dashboardName}"]`).click();
cy.getBySel('btn-modal-save').click();
});
cy.getBySelLike('chart-modal').should('be.visible');
cy.get(
'[data-test="save-chart-modal-select-dashboard-form"] [aria-label="Select a dashboard"]',
)
.first()
.click();
cy.get(
'.ant-select-selection-search-input[aria-label="Select a dashboard"]',
).type(dashboardName, { force: true });
cy.get(`.ant-select-item-option[title="${dashboardName}"]`).click();
cy.getBySel('btn-modal-save').click();
cy.wait('@update');
cy.wait('@get');

View File

@@ -252,215 +252,4 @@ describe('Visualization > Table', () => {
});
cy.get('td').contains(/\d*%/);
});
it('Test row limit with server pagination toggle', () => {
cy.visitChartByParams({
...VIZ_DEFAULTS,
metrics: ['count'],
row_limit: 100,
});
// Enable server pagination
cy.get('[data-test="server_pagination-header"] div.pull-left').click();
// Click row limit control and select high value (200k)
cy.get('div[aria-label="Row limit"]').click();
// Type 200000 and press enter to select the option
cy.get('div[aria-label="Row limit"]')
.find('.ant-select-selection-search-input:visible')
.type('200000{enter}');
// Verify that there is no error tooltip when server pagination is enabled
cy.get('[data-test="error-tooltip"]').should('not.exist');
// Disable server pagination
cy.get('[data-test="server_pagination-header"] div.pull-left').click();
// Verify error tooltip appears
cy.get('[data-test="error-tooltip"]').should('be.visible');
// Trigger mouseover and verify tooltip text
cy.get('[data-test="error-tooltip"]').trigger('mouseover');
// Verify tooltip content
cy.get('.antd5-tooltip-inner').should('be.visible');
cy.get('.antd5-tooltip-inner').should(
'contain',
'Server pagination needs to be enabled for values over',
);
// Hide the tooltip by adding display:none style
cy.get('.antd5-tooltip').invoke('attr', 'style', 'display: none');
// Enable server pagination again
cy.get('[data-test="server_pagination-header"] div.pull-left').click();
cy.get('[data-test="error-tooltip"]').should('not.exist');
cy.get('div[aria-label="Row limit"]').click();
// Type 1000000
cy.get('div[aria-label="Row limit"]')
.find('.ant-select-selection-search-input:visible')
.type('1000000');
// Wait for 1 second
cy.wait(1000);
// Press enter
cy.get('div[aria-label="Row limit"]')
.find('.ant-select-selection-search-input:visible')
.type('{enter}');
// Wait for error tooltip to appear and verify its content
cy.get('[data-test="error-tooltip"]')
.should('be.visible')
.trigger('mouseover');
// Wait for tooltip content and verify
cy.get('.antd5-tooltip-inner').should('exist');
cy.get('.antd5-tooltip-inner').should('be.visible');
// Verify tooltip content separately
cy.get('.antd5-tooltip-inner').should('contain', 'Value cannot exceed');
});
it('Test sorting with server pagination enabled', () => {
cy.visitChartByParams({
...VIZ_DEFAULTS,
metrics: ['count'],
groupby: ['name'],
row_limit: 100000,
server_pagination: true, // Enable server pagination
});
// Wait for the initial data load
cy.wait('@chartData');
// Get the first column header (name)
cy.get('.chart-container th').contains('name').as('nameHeader');
// Click to sort ascending
cy.get('@nameHeader').click();
cy.wait('@chartData');
// Verify first row starts with 'A'
cy.get('.chart-container td:first').invoke('text').should('match', /^[Aa]/);
// Click again to sort descending
cy.get('@nameHeader').click();
cy.wait('@chartData');
// Verify first row starts with 'Z'
cy.get('.chart-container td:first').invoke('text').should('match', /^[Zz]/);
// Test numeric sorting
cy.get('.chart-container th').contains('COUNT').as('countHeader');
// Click to sort ascending by count
cy.get('@countHeader').click();
cy.wait('@chartData');
// Get first two count values and verify ascending order
cy.get('.chart-container td:nth-child(2)').then($cells => {
const first = parseFloat($cells[0].textContent || '0');
const second = parseFloat($cells[1].textContent || '0');
expect(first).to.be.at.most(second);
});
// Click again to sort descending
cy.get('@countHeader').click();
cy.wait('@chartData');
// Get first two count values and verify descending order
cy.get('.chart-container td:nth-child(2)').then($cells => {
const first = parseFloat($cells[0].textContent || '0');
const second = parseFloat($cells[1].textContent || '0');
expect(first).to.be.at.least(second);
});
});
it('Test search with server pagination enabled', () => {
cy.visitChartByParams({
...VIZ_DEFAULTS,
metrics: ['count'],
groupby: ['name', 'state'],
row_limit: 100000,
server_pagination: true,
include_search: true,
});
cy.wait('@chartData');
// Basic search test
cy.get('span.dt-global-filter input.form-control.input-sm').should(
'be.visible',
);
cy.get('span.dt-global-filter input.form-control.input-sm').type('John');
cy.wait('@chartData');
cy.get('.chart-container tbody tr').each($row => {
cy.wrap($row).contains(/John/i);
});
// Clear and test case-insensitive search
cy.get('span.dt-global-filter input.form-control.input-sm').clear();
cy.wait('@chartData');
cy.get('span.dt-global-filter input.form-control.input-sm').type('mary');
cy.wait('@chartData');
cy.get('.chart-container tbody tr').each($row => {
cy.wrap($row).contains(/Mary/i);
});
// Test special characters
cy.get('span.dt-global-filter input.form-control.input-sm').clear();
cy.get('span.dt-global-filter input.form-control.input-sm').type('Nicole');
cy.wait('@chartData');
cy.get('.chart-container tbody tr').each($row => {
cy.wrap($row).contains(/Nicole/i);
});
// Test no results
cy.get('span.dt-global-filter input.form-control.input-sm').clear();
cy.get('span.dt-global-filter input.form-control.input-sm').type('XYZ123');
cy.wait('@chartData');
cy.get('.chart-container').contains('No records found');
// Test column-specific search
cy.get('.search-select').should('be.visible');
cy.get('.search-select').click();
cy.get('.ant-select-dropdown').should('be.visible');
cy.get('.ant-select-item-option').contains('state').should('be.visible');
cy.get('.ant-select-item-option').contains('state').click();
cy.get('span.dt-global-filter input.form-control.input-sm').clear();
cy.get('span.dt-global-filter input.form-control.input-sm').type('CA');
cy.wait('@chartData');
cy.wait(1000);
cy.get('td[aria-labelledby="header-state"]').should('be.visible');
cy.get('td[aria-labelledby="header-state"]')
.first()
.should('contain', 'CA');
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -90,7 +90,7 @@
"@reduxjs/toolkit": "^1.9.3",
"@rjsf/core": "^5.21.1",
"@rjsf/utils": "^5.24.3",
"@rjsf/validator-ajv8": "^5.24.9",
"@rjsf/validator-ajv8": "^5.22.3",
"@scarf/scarf": "^1.4.0",
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
"@superset-ui/core": "file:./packages/superset-ui-core",
@@ -122,7 +122,7 @@
"@visx/tooltip": "^3.0.0",
"@visx/xychart": "^3.5.1",
"abortcontroller-polyfill": "^1.7.8",
"ace-builds": "^1.41.0",
"ace-builds": "^1.36.3",
"ag-grid-community": "33.1.1",
"ag-grid-react": "33.1.1",
"antd": "4.10.3",
@@ -137,7 +137,6 @@
"dayjs": "^1.11.13",
"dom-to-image-more": "^3.2.0",
"dom-to-pdf": "^0.3.2",
"dompurify": "^3.2.4",
"echarts": "^5.6.0",
"emotion-rgba": "0.0.12",
"eslint-plugin-i18n-strings": "file:eslint-rules/eslint-plugin-i18n-strings",
@@ -183,7 +182,7 @@
"react-dom": "^17.0.2",
"react-draggable": "^4.4.6",
"react-hot-loader": "^4.13.1",
"react-intersection-observer": "^9.16.0",
"react-intersection-observer": "^9.10.2",
"react-js-cron": "^2.1.2",
"react-json-tree": "^0.17.0",
"react-lines-ellipsis": "^0.15.4",
@@ -230,8 +229,8 @@
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-modules-commonjs": "^7.26.3",
"@babel/plugin-transform-runtime": "^7.27.1",
"@babel/preset-env": "^7.27.2",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/preset-env": "^7.26.7",
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.26.0",
"@babel/register": "^7.23.7",
@@ -261,6 +260,7 @@
"@testing-library/user-event": "^12.8.3",
"@types/classnames": "^2.2.10",
"@types/dom-to-image": "^2.6.7",
"@types/enzyme": "^3.10.18",
"@types/fetch-mock": "^7.3.2",
"@types/jest": "^29.5.12",
"@types/jquery": "^3.5.8",
@@ -290,8 +290,9 @@
"@types/yargs": "12 - 18",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
"babel-jest": "^29.7.0",
"babel-loader": "^10.0.0",
"babel-loader": "^9.1.3",
"babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
"babel-plugin-lodash": "^3.3.4",
@@ -301,6 +302,8 @@
"cross-env": "^7.0.3",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.2",
"enzyme": "^3.11.0",
"enzyme-matchers": "^7.1.2",
"eslint": "^8.56.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^7.2.0",

View File

@@ -25,6 +25,7 @@
],
"dependencies": {
"@react-icons/all-files": "^4.1.0",
"@types/enzyme": "^3.10.18",
"@types/react": "*",
"lodash": "^4.17.21",
"prop-types": "^15.8.1"

View File

@@ -94,8 +94,9 @@ export function ControlHeader({
<label className="control-label" htmlFor={name}>
{leftNode && <span>{leftNode}</span>}
<span
role={onClick ? 'button' : undefined}
{...(onClick ? { onClick, tabIndex: 0 } : {})}
role="button"
tabIndex={0}
onClick={onClick}
className={labelClass}
style={{ cursor: onClick ? 'pointer' : '' }}
>

View File

@@ -26,7 +26,6 @@ import {
import { ColumnMeta, SortSeriesData, SortSeriesType } from './types';
export const DEFAULT_MAX_ROW = 100000;
export const DEFAULT_MAX_ROW_TABLE_SERVER = 500000;
// eslint-disable-next-line import/prefer-default-export
export const TIME_FILTER_LABELS = {

View File

@@ -21,6 +21,7 @@ import { Dataset } from './types';
export const TestDataset: Dataset = {
column_formats: {},
currency_formats: {},
columns: [
{
advanced_data_type: undefined,

View File

@@ -30,7 +30,7 @@ export const aggregationOperator: PostProcessingFactory<
> = (formData: QueryFormData, queryObject) => {
const { aggregation = 'LAST_VALUE' } = formData;
if (aggregation === 'LAST_VALUE' || aggregation === 'raw') {
if (aggregation === 'LAST_VALUE') {
return undefined;
}

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import { ReactNode } from 'react';
import { JsonValue, t, useTheme } from '@superset-ui/core';
import { JsonValue, useTheme } from '@superset-ui/core';
import { ControlHeader } from '../../components/ControlHeader';
// [value, label]
@@ -69,24 +69,17 @@ export default function RadioButtonControl({
boxShadow: 'none',
},
}}
role="tablist"
aria-label={typeof props.label === 'string' ? props.label : undefined}
>
<ControlHeader {...props} />
<div className="btn-group btn-group-sm">
{options.map(([val, label]) => (
<button
aria-label={typeof label === 'string' ? label : undefined}
id={`tab-${val}`}
key={JSON.stringify(val)}
type="button"
aria-selected={val === currentValue}
role="tab"
className={`btn btn-default ${
val === currentValue ? 'active' : ''
}`}
onClick={e => {
e.currentTarget?.focus();
onClick={() => {
onChange(val);
}}
>
@@ -94,23 +87,6 @@ export default function RadioButtonControl({
</button>
))}
</div>
{/* accessibility begin */}
<div
aria-live="polite"
style={{
position: 'absolute',
left: '-9999px',
height: '1px',
width: '1px',
overflow: 'hidden',
}}
>
{t(
'%s tab selected',
options.find(([val]) => val === currentValue)?.[1],
)}
</div>
{/* accessibility end */}
</div>
);
}

View File

@@ -70,7 +70,6 @@ export const aggregationControl = {
clearable: false,
renderTrigger: false,
choices: [
['raw', t('None')],
['LAST_VALUE', t('Last Value')],
['sum', t('Total (Sum)')],
['mean', t('Average (Mean)')],
@@ -78,9 +77,7 @@ export const aggregationControl = {
['max', t('Maximum')],
['median', t('Median')],
],
description: t(
'Aggregation method used to compute the Big Number from the Trendline.For non-additive metrics like ratios, averages, distinct counts, etc use NONE.',
),
description: t('Select an aggregation method to apply to the metric.'),
provideFormDataToProps: true,
mapStateToProps: ({ form_data }: ControlPanelState) => ({
value: form_data.aggregation || 'LAST_VALUE',

View File

@@ -69,7 +69,7 @@ export interface Dataset {
columns: ColumnMeta[];
metrics: Metric[];
column_formats: Record<string, string>;
currency_formats?: Record<string, Currency>;
currency_formats: Record<string, Currency>;
verbose_map: Record<string, string>;
main_dttm_col: string;
// eg. ['["ds", true]', 'ds [asc]']

View File

@@ -53,6 +53,7 @@ describe('columnChoices()', () => {
],
verbose_map: {},
column_formats: { fiz: 'NUMERIC', about: 'STRING', foo: 'DATE' },
currency_formats: {},
datasource_name: 'my_datasource',
description: 'this is my datasource',
}),
@@ -104,6 +105,7 @@ describe('columnChoices()', () => {
],
verbose_map: {},
column_formats: { fiz: 'NUMERIC', about: 'STRING', foo: 'DATE' },
currency_formats: {},
datasource_name: 'my_datasource',
description: 'this is my datasource',
}),

View File

@@ -41,6 +41,7 @@ describe('defineSavedMetrics', () => {
columns: [],
verbose_map: {},
column_formats: {},
currency_formats: {},
datasource_name: 'my_datasource',
description: 'this is my datasource',
};

View File

@@ -56,6 +56,7 @@
"@types/d3-scale": "^2.1.1",
"@types/d3-time": "^3.0.4",
"@types/d3-time-format": "^4.0.3",
"@types/enzyme": "^3.10.18",
"@types/fetch-mock": "^7.3.8",
"@types/lodash": "^4.17.16",
"@types/math-expression-evaluator": "^1.3.3",

View File

@@ -49,7 +49,6 @@ export interface ChartMetadataConfig {
label?: ChartLabel | null;
labelExplanation?: string | null;
queryObjectCount?: number;
dynamicQueryObjectCount?: boolean;
parseMethod?: ParseMethod;
// suppressContextMenu: true hides the default context menu for the chart.
// This is useful for viz plugins that define their own context menu.
@@ -93,8 +92,6 @@ export default class ChartMetadata {
queryObjectCount: number;
dynamicQueryObjectCount: boolean;
parseMethod: ParseMethod;
suppressContextMenu?: boolean;
@@ -118,7 +115,6 @@ export default class ChartMetadata {
label = null,
labelExplanation = null,
queryObjectCount = 1,
dynamicQueryObjectCount = false,
parseMethod = 'json-bigint',
suppressContextMenu = false,
} = config;
@@ -149,7 +145,6 @@ export default class ChartMetadata {
this.label = label;
this.labelExplanation = labelExplanation;
this.queryObjectCount = queryObjectCount;
this.dynamicQueryObjectCount = dynamicQueryObjectCount;
this.parseMethod = parseMethod;
this.suppressContextMenu = suppressContextMenu;
}

View File

@@ -67,8 +67,6 @@ type Hooks = {
setDataMask?: SetDataMaskHook;
/** handle tooltip */
setTooltip?: HandlerFunction;
/* handle legend scroll changes */
onLegendScroll?: HandlerFunction;
} & PlainObject;
/**
@@ -107,8 +105,6 @@ export interface ChartPropsConfig {
inputRef?: RefObject<any>;
/** Theme object */
theme: SupersetTheme;
/* legend index */
legendIndex?: number;
}
const DEFAULT_WIDTH = 800;
@@ -139,8 +135,6 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
legendState?: LegendState;
legendIndex?: number;
queriesData: QueryData[];
width: number;
@@ -170,7 +164,6 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
ownState = {},
filterState = {},
legendState,
legendIndex,
initialValues = {},
queriesData = [],
behaviors = [],
@@ -197,7 +190,6 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
this.ownState = ownState;
this.filterState = filterState;
this.legendState = legendState;
this.legendIndex = legendIndex;
this.behaviors = behaviors;
this.displaySettings = displaySettings;
this.appSection = appSection;
@@ -223,7 +215,6 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
input => input.ownState,
input => input.filterState,
input => input.legendState,
input => input.legendIndex,
input => input.behaviors,
input => input.displaySettings,
input => input.appSection,
@@ -244,7 +235,6 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
ownState,
filterState,
legendState,
legendIndex,
behaviors,
displaySettings,
appSection,
@@ -265,7 +255,6 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
ownState,
filterState,
legendState,
legendIndex,
width,
behaviors,
displaySettings,

View File

@@ -58,18 +58,17 @@ export default async function parseResponse<T extends ParseMethod = 'json'>(
const result: JsonResponse = {
response,
json: cloneDeepWith(json, (value: any) => {
// `json-bigint` could not handle floats well, see sidorares/json-bigint#62
// TODO: clean up after json-bigint>1.0.1 is released
if (value?.isInteger?.() === false) {
return Number(value);
}
if (
value?.isInteger?.() === true &&
(value?.isGreaterThan?.(Number.MAX_SAFE_INTEGER) ||
value?.isLessThan?.(Number.MIN_SAFE_INTEGER))
value?.isGreaterThan?.(Number.MAX_SAFE_INTEGER) ||
value?.isLessThan?.(Number.MIN_SAFE_INTEGER)
) {
return BigInt(value);
}
// // `json-bigint` could not handle floats well, see sidorares/json-bigint#62
// // TODO: clean up after json-bigint>1.0.1 is released
if (value?.isNaN?.() === false) {
return value?.toNumber?.();
}
return undefined;
}),
};

View File

@@ -97,7 +97,6 @@ interface _PostProcessingContribution {
orientation?: 'row' | 'column';
columns?: string[];
rename_columns?: string[];
contribution_totals?: Record<string, number>;
};
}
export type PostProcessingContribution =

View File

@@ -17,7 +17,6 @@
* under the License.
*/
import { t } from '../translation';
import { sanitizeHtml } from './html';
const TRUNCATION_STYLE = `
max-width: 300px;
@@ -33,7 +32,7 @@ export function tooltipHtml(
const titleRow = title
? `<span style="font-weight: 700;${TRUNCATION_STYLE}">${title}</span>`
: '';
return sanitizeHtml(`
return `
<div>
${titleRow}
<table>
@@ -54,5 +53,5 @@ export function tooltipHtml(
})
.join('')}
</table>
</div>`);
</div>`;
}

View File

@@ -25,4 +25,3 @@ export { default as validateNonEmpty } from './validateNonEmpty';
export { default as validateMaxValue } from './validateMaxValue';
export { default as validateMapboxStylesUrl } from './validateMapboxStylesUrl';
export { default as validateTimeComparisonRangeValues } from './validateTimeComparisonRangeValues';
export { default as validateServerPagination } from './validateServerPagination';

View File

@@ -143,7 +143,7 @@ describe('parseResponse()', () => {
const mockBigIntUrl = '/mock/get/bigInt';
const mockGetBigIntPayload = `{
"value": 9223372036854775807, "minus": { "value": -483729382918228373892, "str": "something" },
"number": 1234, "floatValue": { "plus": 0.3452211361231223, "minus": -0.3452211361231223, "even": 1234567890123456.0000000 },
"number": 1234, "floatValue": { "plus": 0.3452211361231223, "minus": -0.3452211361231223 },
"string.constructor": "data.constructor",
"constructor": "constructor"
}`;
@@ -161,7 +161,6 @@ describe('parseResponse()', () => {
expect(responseBigNumber.json.floatValue.minus).toEqual(
-0.3452211361231223,
);
expect(responseBigNumber.json.floatValue.even).toEqual(1234567890123456);
expect(
responseBigNumber.json.floatValue.plus +
responseBigNumber.json.floatValue.minus,

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { sanitizeHtml, tooltipHtml } from '@superset-ui/core';
import { tooltipHtml } from '@superset-ui/core';
const TITLE_STYLE =
'style="font-weight: 700;max-width:300px;overflow:hidden;text-overflow:ellipsis;"';
@@ -39,8 +39,7 @@ function removeWhitespaces(text: string) {
test('should return a table with the given data', () => {
const title = 'Title';
const html = removeWhitespaces(tooltipHtml(data, title));
const expectedHtml = removeWhitespaces(
sanitizeHtml(`
const expectedHtml = removeWhitespaces(`
<div>
<span ${TITLE_STYLE}>Title</span>
<table>
@@ -55,8 +54,7 @@ test('should return a table with the given data', () => {
<td ${TD_NUMBER_STYLE}>3</td>
</tr>
</table>
</div>`),
);
</div>`);
expect(html).toMatch(expectedHtml);
});
@@ -64,8 +62,7 @@ test('should return a table with the given data and a focused row', () => {
const title = 'Title';
const focusedRow = 1;
const html = removeWhitespaces(tooltipHtml(data, title, focusedRow));
const expectedHtml = removeWhitespaces(
sanitizeHtml(`
const expectedHtml = removeWhitespaces(`
<div>
<span ${TITLE_STYLE}>Title</span>
<table>
@@ -80,30 +77,26 @@ test('should return a table with the given data and a focused row', () => {
<td ${TD_NUMBER_STYLE}>3</td>
</tr>
</table>
</div>`),
);
</div>`);
expect(html).toMatch(expectedHtml);
});
test('should return a table with no data', () => {
const title = 'Title';
const html = removeWhitespaces(tooltipHtml([], title));
const expectedHtml = removeWhitespaces(
sanitizeHtml(`
const expectedHtml = removeWhitespaces(`
<div>
<span ${TITLE_STYLE}>Title</span>
<table>
<tr><td>No data</td></tr>
</table>
</div>`),
);
</div>`);
expect(html).toMatch(expectedHtml);
});
test('should return a table with the given data and no title', () => {
const html = removeWhitespaces(tooltipHtml(data));
const expectedHtml = removeWhitespaces(
sanitizeHtml(`
const expectedHtml = removeWhitespaces(`
<div>
<table>
<tr ${TR_STYLE}>
@@ -117,36 +110,6 @@ test('should return a table with the given data and no title', () => {
<td ${TD_NUMBER_STYLE}>3</td>
</tr>
</table>
</div>`),
);
expect(html).toMatch(expectedHtml);
});
test('should sanitize HTML input', () => {
const title = 'Title<script>alert("message");</script>';
const data = [
['<b onclick="alert(\'message\')">B message</b>', 'message2'],
['<img src="x" onerror="alert(\'message\');" />', '<i>Italic</i>'],
];
const html = removeWhitespaces(tooltipHtml(data, title));
const expectedHtml = removeWhitespaces(
sanitizeHtml(`
<div>
<span ${TITLE_STYLE}>Titlealert("message");</span>
<table>
<tr ${TR_STYLE}>
<td ${TD_TEXT_STYLE}><b>B message</b></td>
<td ${TD_NUMBER_STYLE}>message2</td>
</tr>
<tr ${TR_STYLE}>
<td ${TD_TEXT_STYLE}><img src="x" /></td>
<td ${TD_NUMBER_STYLE}><i>Italic</i></td>
</tr>
</table>
</div>`),
);
</div>`);
expect(html).toMatch(expectedHtml);
});

View File

@@ -1,153 +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 { validateServerPagination } from '@superset-ui/core';
import './setup';
const DEFAULT_MAX_ROW = 100000;
const DEFAULT_MAX_ROW_TABLE_SERVER = 500000;
test('validateServerPagination returns warning message only when value is between max thresholds and server pagination is disabled', () => {
// Should show warning - value between thresholds and server pagination disabled
expect(
validateServerPagination(
200000,
false,
DEFAULT_MAX_ROW,
DEFAULT_MAX_ROW_TABLE_SERVER,
),
).toBeTruthy();
expect(
validateServerPagination(
300000,
false,
DEFAULT_MAX_ROW,
DEFAULT_MAX_ROW_TABLE_SERVER,
),
).toBeTruthy();
// Should not show warning - value above max server threshold
expect(
validateServerPagination(
600000,
false,
DEFAULT_MAX_ROW,
DEFAULT_MAX_ROW_TABLE_SERVER,
),
).toBeFalsy();
// Should not show warning - value below max without server threshold
expect(
validateServerPagination(
50000,
false,
DEFAULT_MAX_ROW,
DEFAULT_MAX_ROW_TABLE_SERVER,
),
).toBeFalsy();
});
test('validateServerPagination returns false when server pagination is enabled regardless of value', () => {
expect(
validateServerPagination(
200000,
true,
DEFAULT_MAX_ROW,
DEFAULT_MAX_ROW_TABLE_SERVER,
),
).toBeFalsy();
expect(
validateServerPagination(
300000,
true,
DEFAULT_MAX_ROW,
DEFAULT_MAX_ROW_TABLE_SERVER,
),
).toBeFalsy();
expect(
validateServerPagination(
600000,
true,
DEFAULT_MAX_ROW,
DEFAULT_MAX_ROW_TABLE_SERVER,
),
).toBeFalsy();
});
test('validateServerPagination handles string inputs correctly', () => {
expect(
validateServerPagination(
'200000',
false,
DEFAULT_MAX_ROW,
DEFAULT_MAX_ROW_TABLE_SERVER,
),
).toBeTruthy();
expect(
validateServerPagination(
'600000',
false,
DEFAULT_MAX_ROW,
DEFAULT_MAX_ROW_TABLE_SERVER,
),
).toBeFalsy();
expect(
validateServerPagination(
'50000',
false,
DEFAULT_MAX_ROW,
DEFAULT_MAX_ROW_TABLE_SERVER,
),
).toBeFalsy();
});
test('validateServerPagination handles edge cases', () => {
expect(
validateServerPagination(
undefined,
false,
DEFAULT_MAX_ROW,
DEFAULT_MAX_ROW_TABLE_SERVER,
),
).toBeFalsy();
expect(
validateServerPagination(
null,
false,
DEFAULT_MAX_ROW,
DEFAULT_MAX_ROW_TABLE_SERVER,
),
).toBeFalsy();
expect(
validateServerPagination(
NaN,
false,
DEFAULT_MAX_ROW,
DEFAULT_MAX_ROW_TABLE_SERVER,
),
).toBeFalsy();
expect(
validateServerPagination(
'invalid',
false,
DEFAULT_MAX_ROW,
DEFAULT_MAX_ROW_TABLE_SERVER,
),
).toBeFalsy();
});

View File

@@ -54,13 +54,13 @@
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/preset-env": "^7.27.2",
"@babel/preset-env": "^7.26.7",
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.23.3",
"@storybook/react-webpack5": "8.2.9",
"babel-loader": "^10.0.0",
"babel-loader": "^9.1.3",
"fork-ts-checker-webpack-plugin": "^9.0.2",
"ts-loader": "^9.5.2",
"ts-loader": "^9.5.1",
"typescript": "^5.7.2"
},
"peerDependencies": {

View File

@@ -136,7 +136,7 @@ const CategoricalDeckGLContainer = (props: CategoricalDeckGLContainerProps) => {
return data.map(d => {
let color;
if (fd.dimension) {
color = hexToRGB(colorFn(d.cat_color, fd.slice_id), c.a * 255);
color = hexToRGB(colorFn(d.cat_color, fd.sliceId), c.a * 255);
return { ...d, color };
}

View File

@@ -38,20 +38,9 @@ import {
} from '../DeckGLContainer';
import { getExploreLongUrl } from '../utils/explore';
import layerGenerators from '../layers';
import fitViewport, { Viewport } from '../utils/fitViewport';
import { Viewport } from '../utils/fitViewport';
import { TooltipProps } from '../components/Tooltip';
import { getPoints as getPointsArc } from '../layers/Arc/Arc';
import { getPoints as getPointsPath } from '../layers/Path/Path';
import { getPoints as getPointsPolygon } from '../layers/Polygon/Polygon';
import { getPoints as getPointsGrid } from '../layers/Grid/Grid';
import { getPoints as getPointsScatter } from '../layers/Scatter/Scatter';
import { getPoints as getPointsContour } from '../layers/Contour/Contour';
import { getPoints as getPointsHeatmap } from '../layers/Heatmap/Heatmap';
import { getPoints as getPointsHex } from '../layers/Hex/Hex';
import { getPoints as getPointsGeojson } from '../layers/Geojson/Geojson';
import { getPoints as getPointsScreengrid } from '../layers/Screengrid/Screengrid';
export type DeckMultiProps = {
formData: QueryFormData;
payload: JsonObject;
@@ -67,35 +56,7 @@ export type DeckMultiProps = {
const DeckMulti = (props: DeckMultiProps) => {
const containerRef = useRef<DeckGLContainerHandle>();
const getAdjustedViewport = useCallback(() => {
let viewport = { ...props.viewport };
const points = [
...getPointsPolygon(props.payload.data.features.deck_polygon || []),
...getPointsPath(props.payload.data.features.deck_path || []),
...getPointsGrid(props.payload.data.features.deck_grid || []),
...getPointsScatter(props.payload.data.features.deck_scatter || []),
...getPointsContour(props.payload.data.features.deck_contour || []),
...getPointsHeatmap(props.payload.data.features.deck_heatmap || []),
...getPointsHex(props.payload.data.features.deck_hex || []),
...getPointsArc(props.payload.data.features.deck_arc || []),
...getPointsGeojson(props.payload.data.features.deck_geojson || []),
...getPointsScreengrid(props.payload.data.features.deck_screengrid || []),
];
if (props.formData) {
viewport = fitViewport(viewport, {
width: props.width,
height: props.height,
points,
});
}
if (viewport.zoom < 0) {
viewport.zoom = 0;
}
return viewport;
}, [props]);
const [viewport, setViewport] = useState<Viewport>(getAdjustedViewport());
const [viewport, setViewport] = useState<Viewport>();
const [subSlicesLayers, setSubSlicesLayers] = useState<Record<number, Layer>>(
{},
);
@@ -109,31 +70,23 @@ const DeckMulti = (props: DeckMultiProps) => {
const loadLayers = useCallback(
(formData: QueryFormData, payload: JsonObject, viewport?: Viewport) => {
setViewport(getAdjustedViewport());
setViewport(viewport);
setSubSlicesLayers({});
payload.data.slices.forEach(
(subslice: { slice_id: number } & JsonObject) => {
// Filters applied to multi_deck are passed down to underlying charts
// note that dashboard contextual information (filter_immune_slices and such) aren't
// taken into consideration here
const extra_filters = [
...(subslice.form_data.extra_filters || []),
const filters = [
...(subslice.form_data.filters || []),
...(formData.filters || []),
...(formData.extra_filters || []),
...(formData.extra_form_data?.filters || []),
];
const adhoc_filters = [
...(formData.adhoc_filters || []),
...(subslice.formData?.adhoc_filters || []),
...(formData.extra_form_data?.adhoc_filters || []),
];
const subsliceCopy = {
...subslice,
form_data: {
...subslice.form_data,
extra_filters,
adhoc_filters,
filters,
},
};
@@ -164,13 +117,7 @@ const DeckMulti = (props: DeckMultiProps) => {
},
);
},
[
props.datasource,
props.onAddFilter,
props.onSelect,
setTooltip,
getAdjustedViewport,
],
[props.datasource, props.onAddFilter, props.onSelect, setTooltip],
);
const prevDeckSlices = usePrevious(props.formData.deck_slices);
@@ -189,7 +136,7 @@ const DeckMulti = (props: DeckMultiProps) => {
<DeckGLContainerStyledWrapper
ref={containerRef}
mapboxApiAccessToken={payload.data.mapboxApiKey}
viewport={viewport}
viewport={viewport || props.viewport}
layers={layers}
mapStyle={formData.mapbox_style}
setControlValue={setControlValue}

View File

@@ -29,7 +29,7 @@ import TooltipRow from '../../TooltipRow';
import { TooltipProps } from '../../components/Tooltip';
import { Point } from '../../types';
export function getPoints(data: JsonObject[]) {
function getPoints(data: JsonObject[]) {
const points: Point[] = [];
data.forEach(d => {
points.push(d.sourcePosition);

View File

@@ -97,7 +97,7 @@ export const getLayer: getLayerType<unknown> = function (
});
};
export function getPoints(data: any[]) {
function getPoints(data: any[]) {
return data.map(d => d.position);
}

View File

@@ -39,7 +39,6 @@ import { commonLayerProps } from '../common';
import TooltipRow from '../../TooltipRow';
import fitViewport, { Viewport } from '../../utils/fitViewport';
import { TooltipProps } from '../../components/Tooltip';
import { Point } from '../../types';
type ProcessedFeature = Feature<Geometry, GeoJsonProperties> & {
properties: JsonObject;
@@ -173,17 +172,6 @@ export type DeckGLGeoJsonProps = {
width: number;
};
export function getPoints(data: Point[]) {
return data.reduce((acc: Array<any>, feature: any) => {
const bounds = geojsonExtent(feature);
if (bounds) {
return [...acc, [bounds[0], bounds[1]], [bounds[2], bounds[3]]];
}
return acc;
}, []);
}
const DeckGLGeoJson = (props: DeckGLGeoJsonProps) => {
const containerRef = useRef<DeckGLContainerHandle>();
const setTooltip = useCallback((tooltip: TooltipProps['tooltip']) => {
@@ -198,13 +186,24 @@ const DeckGLGeoJson = (props: DeckGLGeoJsonProps) => {
const viewport: Viewport = useMemo(() => {
if (formData.autozoom) {
const points = getPoints(payload.data.features) || [];
const points =
payload?.data?.features?.reduce?.(
(acc: [number, number, number, number][], feature: any) => {
const bounds = geojsonExtent(feature);
if (bounds) {
return [...acc, [bounds[0], bounds[1]], [bounds[2], bounds[3]]];
}
return acc;
},
[],
) || [];
if (points.length) {
return fitViewport(props.viewport, {
width,
height,
points: getPoints(payload.data.features) || [],
points,
});
}
}

View File

@@ -86,7 +86,7 @@ export function getLayer(
});
}
export function getPoints(data: JsonObject[]) {
function getPoints(data: JsonObject[]) {
return data.map(d => d.position);
}

View File

@@ -79,7 +79,7 @@ export const getLayer: getLayerType<unknown> = (
});
};
export function getPoints(data: any[]) {
function getPoints(data: any[]) {
return data.map(d => d.position);
}

View File

@@ -84,7 +84,7 @@ export function getLayer(
});
}
export function getPoints(data: JsonObject[]) {
function getPoints(data: JsonObject[]) {
return data.map(d => d.position);
}

View File

@@ -76,7 +76,7 @@ export function getLayer(
});
}
export function getPoints(data: JsonObject[]) {
function getPoints(data: JsonObject[]) {
let points: Point[] = [];
data.forEach(d => {
points = points.concat(d.path);

View File

@@ -173,10 +173,6 @@ export type DeckGLPolygonProps = {
height: number;
};
export function getPoints(data: JsonObject[]) {
return data.flatMap(getPointsFromPolygon);
}
const DeckGLPolygon = (props: DeckGLPolygonProps) => {
const containerRef = useRef<DeckGLContainerHandle>();
@@ -187,7 +183,7 @@ const DeckGLPolygon = (props: DeckGLPolygonProps) => {
viewport = fitViewport(viewport, {
width: props.width,
height: props.height,
points: getPoints(features),
points: features.flatMap(getPointsFromPolygon),
});
}
if (viewport.zoom < 0) {

View File

@@ -30,7 +30,7 @@ import TooltipRow from '../../TooltipRow';
import { unitToRadius } from '../../utils/geo';
import { TooltipProps } from '../../components/Tooltip';
export function getPoints(data: JsonObject[]) {
function getPoints(data: JsonObject[]) {
return data.map(d => d.position);
}

View File

@@ -34,7 +34,7 @@ import {
} from '../../DeckGLContainer';
import { TooltipProps } from '../../components/Tooltip';
export function getPoints(data: JsonObject[]) {
function getPoints(data: JsonObject[]) {
return data.map(d => d.position);
}

View File

@@ -31,11 +31,11 @@
"dependencies": {
"d3": "^3.5.17",
"d3-tip": "^0.9.1",
"dompurify": "^3.2.4",
"fast-safe-stringify": "^2.1.1",
"lodash": "^4.17.21",
"dayjs": "^1.11.13",
"nvd3-fork": "^2.0.5",
"dompurify": "^3.2.4",
"prop-types": "^15.8.1",
"urijs": "^1.19.11"
},

View File

@@ -36,25 +36,13 @@ import {
} from './types';
import { useOverflowDetection } from './useOverflowDetection';
const MetricNameText = styled.div<{ metricNameFontSize?: number }>`
${({ theme, metricNameFontSize }) => `
font-family: ${theme.typography.families.sansSerif};
font-weight: ${theme.typography.weights.normal};
font-size: ${metricNameFontSize || theme.typography.sizes.s * 2}px;
text-align: center;
margin-bottom: ${theme.gridUnit * 3}px;
`}
`;
const NumbersContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
overflow: auto;
padding: 12px;
`;
const ComparisonValue = styled.div<PopKPIComparisonValueStyleProps>`
@@ -85,8 +73,6 @@ export default function PopKPI(props: PopKPIProps) {
prevNumber,
valueDifference,
percentDifferenceFormattedString,
metricName,
metricNameFontSize,
headerFontSize,
subheaderFontSize,
comparisonColorEnabled,
@@ -98,8 +84,8 @@ export default function PopKPI(props: PopKPIProps) {
subtitle,
subtitleFontSize,
dashboardTimeRange,
showMetricName,
} = props;
const [comparisonRange, setComparisonRange] = useState<string>('');
useEffect(() => {
@@ -274,16 +260,9 @@ export default function PopKPI(props: PopKPIProps) {
width: fit-content;
margin: auto;
align-items: flex-start;
overflow: auto;
`
}
>
{showMetricName && metricName && (
<MetricNameText metricNameFontSize={metricNameFontSize}>
{metricName}
</MetricNameText>
)}
<div css={bigValueContainerStyles}>
{bigNumber}
{percentDifferenceNumber !== 0 && (

View File

@@ -28,8 +28,6 @@ import {
subheaderFontSize,
subtitleControl,
subtitleFontSize,
showMetricNameControl,
metricNameFontSizeWithVisibility,
} from '../sharedControls';
import { ColorSchemeEnum } from './types';
@@ -72,8 +70,6 @@ const config: ControlPanelConfig = {
],
[subtitleControl],
[subtitleFontSize],
[showMetricNameControl],
[metricNameFontSizeWithVisibility],
[
{
...subheaderFontSize,

View File

@@ -32,7 +32,6 @@ export default class PopKPIPlugin extends ChartPlugin {
tags: [
t('Comparison'),
t('Business'),
t('ECharts'),
t('Percentages'),
t('Report'),
t('Advanced-Analytics'),

View File

@@ -18,7 +18,6 @@
*/
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { Metric } from '@superset-ui/chart-controls';
import {
ChartProps,
getMetricLabel,
@@ -27,13 +26,7 @@ import {
SimpleAdhocFilter,
ensureIsArray,
} from '@superset-ui/core';
import {
getComparisonFontSize,
getHeaderFontSize,
getMetricNameFontSize,
} from './utils';
import { getOriginalLabel } from '../utils';
import { getComparisonFontSize, getHeaderFontSize } from './utils';
dayjs.extend(utc);
@@ -90,7 +83,6 @@ export default function transformProps(chartProps: ChartProps) {
headerFontSize,
headerText,
metric,
metricNameFontSize,
yAxisFormat,
currencyFormat,
subheaderFontSize,
@@ -99,14 +91,11 @@ export default function transformProps(chartProps: ChartProps) {
percentDifferenceFormat,
subtitle = '',
subtitleFontSize,
columnConfig = {},
columnConfig,
} = formData;
const { data: dataA = [] } = queriesData[0];
const data = dataA;
const metricName = metric ? getMetricLabel(metric) : '';
const metrics = chartProps.datasource?.metrics || [];
const originalLabel = getOriginalLabel(metric, metrics);
const showMetricName = chartProps.rawFormData?.show_metric_name ?? false;
const timeComparison = ensureIsArray(chartProps.rawFormData?.time_compare)[0];
const startDateOffset = chartProps.rawFormData?.start_date_offset;
const currentTimeRangeFilter = chartProps.rawFormData?.adhoc_filters?.filter(
@@ -114,13 +103,6 @@ export default function transformProps(chartProps: ChartProps) {
adhoc_filter.operator === 'TEMPORAL_RANGE',
)?.[0];
let metricEntry: Metric | undefined;
if (chartProps.datasource?.metrics) {
metricEntry = chartProps.datasource.metrics.find(
metricItem => metricItem.metric_name === metric,
);
}
const isCustomOrInherit =
timeComparison === 'custom' || timeComparison === 'inherit';
let dataOffset: string[] = [];
@@ -161,7 +143,7 @@ export default function transformProps(chartProps: ChartProps) {
metric,
currencyFormats,
columnFormats,
metricEntry?.d3format || yAxisFormat,
yAxisFormat,
currencyFormat,
);
@@ -197,7 +179,7 @@ export default function transformProps(chartProps: ChartProps) {
width,
height,
data,
metricName: originalLabel,
metricName,
bigNumber,
prevNumber,
valueDifference,
@@ -205,8 +187,6 @@ export default function transformProps(chartProps: ChartProps) {
boldText,
subtitle,
subtitleFontSize,
showMetricName,
metricNameFontSize: getMetricNameFontSize(metricNameFontSize),
headerFontSize: getHeaderFontSize(headerFontSize),
subheaderFontSize: getComparisonFontSize(subheaderFontSize),
headerText,

View File

@@ -61,8 +61,6 @@ export type PopKPIProps = PopKPIStylesProps &
data: TimeseriesDataRecord[];
metrics: Metric[];
metricName: string;
metricNameFontSize?: number;
showMetricName: boolean;
bigNumber: string;
prevNumber: string;
subtitle?: string;

View File

@@ -16,19 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
import {
headerFontSize,
subheaderFontSize,
metricNameFontSize,
} from '../sharedControls';
import { headerFontSize, subheaderFontSize } from '../sharedControls';
const headerFontSizes = [16, 20, 30, 48, 60];
const sharedFontSizes = [16, 20, 26, 32, 40];
const metricNameProportionValues =
metricNameFontSize.config.options.map(
(option: { label: string; value: number }) => option.value,
) ?? [];
const comparisonFontSizes = [16, 20, 26, 32, 40];
const headerProportionValues =
headerFontSize.config.options.map(
@@ -49,10 +40,6 @@ const getFontSizeMapping = (
return acc;
}, {});
const metricNameFontSizesMapping = getFontSizeMapping(
metricNameProportionValues,
sharedFontSizes,
);
const headerFontSizesMapping = getFontSizeMapping(
headerProportionValues,
headerFontSizes,
@@ -60,17 +47,13 @@ const headerFontSizesMapping = getFontSizeMapping(
const comparisonFontSizesMapping = getFontSizeMapping(
subheaderProportionValues,
sharedFontSizes,
comparisonFontSizes,
);
export const getMetricNameFontSize = (proportionValue: number) =>
metricNameFontSizesMapping[proportionValue] ??
sharedFontSizes[sharedFontSizes.length - 1];
export const getHeaderFontSize = (proportionValue: number) =>
headerFontSizesMapping[proportionValue] ??
headerFontSizes[headerFontSizes.length - 1];
export const getComparisonFontSize = (proportionValue: number) =>
comparisonFontSizesMapping[proportionValue] ??
sharedFontSizes[sharedFontSizes.length - 1];
comparisonFontSizes[comparisonFontSizes.length - 1];

View File

@@ -28,8 +28,6 @@ import {
headerFontSize,
subtitleFontSize,
subtitleControl,
showMetricNameControl,
metricNameFontSizeWithVisibility,
} from '../sharedControls';
export default {
@@ -46,8 +44,6 @@ export default {
[headerFontSize],
[subtitleControl],
[subtitleFontSize],
[showMetricNameControl],
[metricNameFontSizeWithVisibility],
['y_axis_format'],
['currency_format'],
[

View File

@@ -39,7 +39,6 @@ const metadata = {
tags: [
t('Additive'),
t('Business'),
t('ECharts'),
t('Legacy'),
t('Percentages'),
t('Featured'),

View File

@@ -36,7 +36,6 @@ jest.mock('@superset-ui/core', () => ({
jest.mock('../utils', () => ({
getDateFormatter: jest.fn(() => (v: any) => `${v}pm`),
parseMetricValue: jest.fn(val => Number(val)),
getOriginalLabel: jest.fn((metric, metrics) => metric),
}));
describe('BigNumberTotal transformProps', () => {

View File

@@ -29,7 +29,7 @@ import {
getValueFormatter,
} from '@superset-ui/core';
import { BigNumberTotalChartProps, BigNumberVizProps } from '../types';
import { getDateFormatter, getOriginalLabel, parseMetricValue } from '../utils';
import { getDateFormatter, parseMetricValue } from '../utils';
import { Refs } from '../../types';
export default function transformProps(
@@ -45,7 +45,6 @@ export default function transformProps(
datasource: { currencyFormats = {}, columnFormats = {} },
} = chartProps;
const {
metricNameFontSize,
headerFontSize,
metric = 'value',
subtitle,
@@ -59,12 +58,9 @@ export default function transformProps(
subheaderFontSize,
} = formData;
const refs: Refs = {};
const { data = [], coltypes = [] } = queriesData[0] || {};
const { data = [], coltypes = [] } = queriesData[0];
const granularity = extractTimegrain(rawFormData as QueryFormData);
const metrics = chartProps.datasource?.metrics || [];
const originalLabel = getOriginalLabel(metric, metrics);
const metricName = getMetricLabel(metric);
const showMetricName = chartProps.rawFormData?.show_metric_name ?? false;
const formattedSubtitle = subtitle?.trim() ? subtitle : subheader || '';
const formattedSubtitleFontSize = subtitle?.trim()
? (subtitleFontSize ?? 1)
@@ -89,7 +85,7 @@ export default function transformProps(
metric,
currencyFormats,
columnFormats,
metricEntry?.d3format || yAxisFormat,
yAxisFormat,
currencyFormat,
);
@@ -107,6 +103,7 @@ export default function transformProps(
const colorThresholdFormatters =
getColorFormatters(conditionalFormatting, data, false) ??
defaultColorFormatters;
return {
width,
height,
@@ -119,8 +116,5 @@ export default function transformProps(
onContextMenu,
refs,
colorThresholdFormatters,
metricName: originalLabel,
showMetricName,
metricNameFontSize,
};
}

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { PureComponent, MouseEvent, createRef } from 'react';
import { PureComponent, MouseEvent } from 'react';
import {
t,
getNumberFormatter,
@@ -35,7 +35,6 @@ const defaultNumberFormatter = getNumberFormatter();
const PROPORTION = {
// text size: proportion of the chart container sans trendline
METRIC_NAME: 0.125,
KICKER: 0.1,
HEADER: 0.3,
SUBHEADER: 0.125,
@@ -43,20 +42,13 @@ const PROPORTION = {
TRENDLINE: 0.3,
};
type BigNumberVisState = {
elementsRendered: boolean;
recalculateTrigger: boolean;
};
class BigNumberVis extends PureComponent<BigNumberVizProps, BigNumberVisState> {
class BigNumberVis extends PureComponent<BigNumberVizProps> {
static defaultProps = {
className: '',
headerFormatter: defaultNumberFormatter,
formatTime: getTimeFormatter(SMART_DATE_VERBOSE_ID),
headerFontSize: PROPORTION.HEADER,
kickerFontSize: PROPORTION.KICKER,
metricNameFontSize: PROPORTION.METRIC_NAME,
showMetricName: true,
mainColor: BRAND_COLOR,
showTimestamp: false,
showTrendLine: false,
@@ -66,40 +58,6 @@ class BigNumberVis extends PureComponent<BigNumberVizProps, BigNumberVisState> {
timeRangeFixed: false,
};
// Create refs for each component to measure heights
metricNameRef = createRef<HTMLDivElement>();
kickerRef = createRef<HTMLDivElement>();
headerRef = createRef<HTMLDivElement>();
subheaderRef = createRef<HTMLDivElement>();
subtitleRef = createRef<HTMLDivElement>();
state = {
elementsRendered: false,
recalculateTrigger: false,
};
componentDidMount() {
// Wait for elements to render and then calculate heights
setTimeout(() => {
this.setState({ elementsRendered: true });
}, 0);
}
componentDidUpdate(prevProps: BigNumberVizProps) {
if (
prevProps.height !== this.props.height ||
prevProps.showTrendLine !== this.props.showTrendLine
) {
this.setState(prevState => ({
recalculateTrigger: !prevState.recalculateTrigger,
}));
}
}
getClassName() {
const { className, showTrendLine, bigNumberFallback } = this.props;
const names = `superset-legacy-chart-big-number ${className} ${
@@ -134,37 +92,6 @@ class BigNumberVis extends PureComponent<BigNumberVizProps, BigNumberVisState> {
);
}
renderMetricName(maxHeight: number) {
const { metricName, width, showMetricName } = this.props;
if (!showMetricName || !metricName) return null;
const text = metricName;
const container = this.createTemporaryContainer();
document.body.append(container);
const fontSize = computeMaxFontSize({
text,
maxWidth: width,
maxHeight,
className: 'metric-name',
container,
});
container.remove();
return (
<div
ref={this.metricNameRef}
className="metric-name"
style={{
fontSize,
height: 'auto',
}}
>
{text}
</div>
);
}
renderKicker(maxHeight: number) {
const { timestamp, showTimestamp, formatTime, width } = this.props;
if (
@@ -191,7 +118,6 @@ class BigNumberVis extends PureComponent<BigNumberVizProps, BigNumberVisState> {
return (
<div
ref={this.kickerRef}
className="kicker"
style={{
fontSize,
@@ -247,7 +173,6 @@ class BigNumberVis extends PureComponent<BigNumberVizProps, BigNumberVisState> {
return (
<div
ref={this.headerRef}
className="header-line"
style={{
display: 'flex',
@@ -286,7 +211,6 @@ class BigNumberVis extends PureComponent<BigNumberVizProps, BigNumberVisState> {
return (
<div
ref={this.subheaderRef}
className="subheader-line"
style={{
fontSize,
@@ -332,7 +256,6 @@ class BigNumberVis extends PureComponent<BigNumberVizProps, BigNumberVisState> {
return (
<>
<div
ref={this.subtitleRef}
className="subtitle-line subheader-line"
style={{
fontSize: `${fontSize}px`,
@@ -393,35 +316,6 @@ class BigNumberVis extends PureComponent<BigNumberVizProps, BigNumberVisState> {
);
}
getTotalElementsHeight() {
const marginPerElement = 8; // theme.gridUnit = 4, so margin-bottom = 8px
const refs = [
this.metricNameRef,
this.kickerRef,
this.headerRef,
this.subheaderRef,
this.subtitleRef,
];
// Filter refs to only those with a current element
const visibleRefs = refs.filter(ref => ref.current);
const totalHeight = visibleRefs.reduce((sum, ref, index) => {
const height = ref.current?.offsetHeight || 0;
const margin = index < visibleRefs.length - 1 ? marginPerElement : 0;
return sum + height + margin;
}, 0);
return totalHeight;
}
shouldApplyOverflow(availableHeight: number) {
if (!this.state.elementsRendered) return false;
const totalHeight = this.getTotalElementsHeight();
return totalHeight > availableHeight;
}
render() {
const {
showTrendLine,
@@ -429,7 +323,6 @@ class BigNumberVis extends PureComponent<BigNumberVizProps, BigNumberVisState> {
kickerFontSize,
headerFontSize,
subtitleFontSize,
metricNameFontSize,
subheaderFontSize,
} = this.props;
const className = this.getClassName();
@@ -437,31 +330,11 @@ class BigNumberVis extends PureComponent<BigNumberVizProps, BigNumberVisState> {
if (showTrendLine) {
const chartHeight = Math.floor(PROPORTION.TRENDLINE * height);
const allTextHeight = height - chartHeight;
const shouldApplyOverflow = this.shouldApplyOverflow(allTextHeight);
return (
<div className={className}>
<div
className="text-container"
style={{
height: allTextHeight,
...(shouldApplyOverflow
? {
display: 'block',
boxSizing: 'border-box',
overflowX: 'hidden',
overflowY: 'auto',
width: '100%',
}
: {}),
}}
>
<div className="text-container" style={{ height: allTextHeight }}>
{this.renderFallbackWarning()}
{this.renderMetricName(
Math.ceil(
(metricNameFontSize || 0) * (1 - PROPORTION.TRENDLINE) * height,
),
)}
{this.renderKicker(
Math.ceil(
(kickerFontSize || 0) * (1 - PROPORTION.TRENDLINE) * height,
@@ -483,33 +356,16 @@ class BigNumberVis extends PureComponent<BigNumberVizProps, BigNumberVisState> {
</div>
);
}
const shouldApplyOverflow = this.shouldApplyOverflow(height);
return (
<div
className={className}
style={{
height,
...(shouldApplyOverflow
? {
display: 'block',
boxSizing: 'border-box',
overflowX: 'hidden',
overflowY: 'auto',
width: '100%',
}
: {}),
}}
>
<div className="text-container">
{this.renderFallbackWarning()}
{this.renderMetricName((metricNameFontSize || 0) * height)}
{this.renderKicker((kickerFontSize || 0) * height)}
{this.renderHeader(Math.ceil(headerFontSize * height))}
{this.rendermetricComparisonSummary(
Math.ceil(subheaderFontSize * height),
)}
{this.renderSubtitle(Math.ceil(subtitleFontSize * height))}
</div>
<div className={className} style={{ height }}>
{this.renderFallbackWarning()}
{this.renderKicker((kickerFontSize || 0) * height)}
{this.renderHeader(Math.ceil(headerFontSize * height))}
{this.rendermetricComparisonSummary(
Math.ceil(subheaderFontSize * height),
)}
{this.renderSubtitle(Math.ceil(subtitleFontSize * height))}
</div>
);
}
@@ -544,12 +400,7 @@ export default styled(BigNumberVis)`
.kicker {
line-height: 1em;
margin-bottom: ${theme.gridUnit * 2}px;
}
.metric-name {
line-height: 1em;
margin-bottom: ${theme.gridUnit * 2}px;
padding-bottom: 2em;
}
.header-line {
@@ -565,12 +416,12 @@ export default styled(BigNumberVis)`
.subheader-line {
line-height: 1em;
margin-bottom: ${theme.gridUnit * 2}px;
padding-bottom: 0;
}
.subtitle-line {
line-height: 1em;
margin-bottom: ${theme.gridUnit * 2}px;
padding-bottom: 0;
}
&.is-fallback-value {

View File

@@ -1,86 +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 { QueryFormData } from '@superset-ui/core';
import buildQuery from './buildQuery';
jest.mock('@superset-ui/core', () => ({
...jest.requireActual('@superset-ui/core'),
getXAxisColumn: jest.fn(() => 'order_date'),
isXAxisSet: jest.fn(() => true),
}));
jest.mock('@superset-ui/chart-controls', () => ({
pivotOperator: jest.fn(() => ({ operation: 'pivot' })),
aggregationOperator: jest.fn(formData => {
if (formData.aggregation === 'LAST_VALUE' || !formData.aggregation) {
return undefined;
}
return {
operation: 'aggregation',
options: { operator: formData.aggregation },
};
}),
flattenOperator: jest.fn(() => ({ operation: 'flatten' })),
resampleOperator: jest.fn(() => ({ operation: 'resample' })),
rollingWindowOperator: jest.fn(() => ({ operation: 'rolling' })),
}));
describe('BigNumberWithTrendline buildQuery', () => {
const baseFormData: QueryFormData = {
datasource: '1__table',
viz_type: 'big_number',
metric: 'custom_metric',
aggregation: null,
};
it('creates raw metric query when aggregation is null', () => {
const queryContext = buildQuery({ ...baseFormData });
const bigNumberQuery = queryContext.queries[1];
expect(bigNumberQuery.post_processing).toEqual([{ operation: 'pivot' }]);
expect(bigNumberQuery.is_timeseries).toBe(true);
});
it('adds aggregation operator when aggregation is "sum"', () => {
const queryContext = buildQuery({ ...baseFormData, aggregation: 'sum' });
const bigNumberQuery = queryContext.queries[1];
expect(bigNumberQuery.post_processing).toEqual([
{ operation: 'pivot' },
{ operation: 'aggregation', options: { operator: 'sum' } },
]);
expect(bigNumberQuery.is_timeseries).toBe(true);
});
it('skips aggregation when aggregation is LAST_VALUE', () => {
const queryContext = buildQuery({
...baseFormData,
aggregation: 'LAST_VALUE',
});
const bigNumberQuery = queryContext.queries[1];
expect(bigNumberQuery.post_processing).toEqual([{ operation: 'pivot' }]);
expect(bigNumberQuery.is_timeseries).toBe(true);
});
it('always returns two queries', () => {
const queryContext = buildQuery({ ...baseFormData });
expect(queryContext.queries.length).toBe(2);
});
});

View File

@@ -16,7 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
import {
buildQueryContext,
ensureIsArray,
@@ -33,17 +32,15 @@ import {
} from '@superset-ui/chart-controls';
export default function buildQuery(formData: QueryFormData) {
const isRawMetric = formData.aggregation === 'raw';
const timeColumn = isXAxisSet(formData)
? ensureIsArray(getXAxisColumn(formData))
: [];
return buildQueryContext(formData, baseQueryObject => [
{
...baseQueryObject,
columns: [...timeColumn],
...(timeColumn.length ? {} : { is_timeseries: true }),
columns: [
...(isXAxisSet(formData)
? ensureIsArray(getXAxisColumn(formData))
: []),
],
...(isXAxisSet(formData) ? {} : { is_timeseries: true }),
post_processing: [
pivotOperator(formData, baseQueryObject),
rollingWindowOperator(formData, baseQueryObject),
@@ -51,16 +48,19 @@ export default function buildQuery(formData: QueryFormData) {
flattenOperator(formData, baseQueryObject),
],
},
{
...baseQueryObject,
columns: [...(isRawMetric ? [] : timeColumn)],
is_timeseries: !isRawMetric,
post_processing: isRawMetric
? []
: [
pivotOperator(formData, baseQueryObject),
aggregationOperator(formData, baseQueryObject),
],
columns: [
...(isXAxisSet(formData)
? ensureIsArray(getXAxisColumn(formData))
: []),
],
...(isXAxisSet(formData) ? {} : { is_timeseries: true }),
post_processing: [
pivotOperator(formData, baseQueryObject),
aggregationOperator(formData, baseQueryObject),
],
},
]);
}

View File

@@ -31,8 +31,6 @@ import {
subheaderFontSize,
subtitleFontSize,
subtitleControl,
showMetricNameControl,
metricNameFontSizeWithVisibility,
} from '../sharedControls';
const config: ControlPanelConfig = {
@@ -143,8 +141,6 @@ const config: ControlPanelConfig = {
[subheaderFontSize],
[subtitleControl],
[subtitleFontSize],
[showMetricNameControl],
[metricNameFontSizeWithVisibility],
['y_axis_format'],
['currency_format'],
[

View File

@@ -37,7 +37,6 @@ const metadata = {
name: t('Big Number with Trendline'),
tags: [
t('Advanced-Analytics'),
t('ECharts'),
t('Line'),
t('Percentages'),
t('Featured'),

View File

@@ -39,7 +39,6 @@ jest.mock('@superset-ui/core', () => ({
jest.mock('../utils', () => ({
getDateFormatter: jest.fn(() => (v: any) => `${v}pm`),
parseMetricValue: jest.fn(val => Number(val)),
getOriginalLabel: jest.fn((metric, metrics) => metric),
}));
jest.mock('../../utils/tooltip', () => ({

View File

@@ -35,7 +35,7 @@ import {
BigNumberWithTrendlineChartProps,
TimeSeriesDatum,
} from '../types';
import { getDateFormatter, parseMetricValue, getOriginalLabel } from '../utils';
import { getDateFormatter, parseMetricValue } from '../utils';
import { getDefaultTooltip } from '../../utils/tooltip';
import { Refs } from '../../types';
@@ -62,7 +62,6 @@ export default function transformProps(
compareLag: compareLag_,
compareSuffix = '',
timeFormat,
metricNameFontSize,
headerFontSize,
metric = 'value',
showTimestamp,
@@ -97,9 +96,6 @@ export default function transformProps(
const aggregatedData = hasAggregatedData ? aggregatedQueryData.data[0] : null;
const refs: Refs = {};
const metricName = getMetricLabel(metric);
const metrics = chartProps.datasource?.metrics || [];
const originalLabel = getOriginalLabel(metric, metrics);
const showMetricName = chartProps.rawFormData?.show_metric_name ?? false;
const compareLag = Number(compareLag_) || 0;
let formattedSubheader = subheader;
@@ -204,7 +200,7 @@ export default function transformProps(
metric,
currencyFormats,
columnFormats,
metricEntry?.d3format || yAxisFormat,
yAxisFormat,
currencyFormat,
);
@@ -307,9 +303,6 @@ export default function transformProps(
headerFormatter,
formatTime,
formData,
metricName: originalLabel,
showMetricName,
metricNameFontSize,
headerFontSize,
subtitleFontSize,
subtitle,

View File

@@ -21,68 +21,106 @@
import { t } from '@superset-ui/core';
import { CustomControlItem } from '@superset-ui/chart-controls';
const FONT_SIZE_OPTIONS_SMALL = [
{ label: t('Tiny'), value: 0.125 },
{ label: t('Small'), value: 0.15 },
{ label: t('Normal'), value: 0.2 },
{ label: t('Large'), value: 0.3 },
{ label: t('Huge'), value: 0.4 },
];
export const headerFontSize: CustomControlItem = {
name: 'header_font_size',
config: {
type: 'SelectControl',
label: t('Big Number Font Size'),
renderTrigger: true,
clearable: false,
default: 0.4,
// Values represent the percentage of space a header should take
options: [
{
label: t('Tiny'),
value: 0.2,
},
{
label: t('Small'),
value: 0.3,
},
{
label: t('Normal'),
value: 0.4,
},
{
label: t('Large'),
value: 0.5,
},
{
label: t('Huge'),
value: 0.6,
},
],
},
};
const FONT_SIZE_OPTIONS_LARGE = [
{ label: t('Tiny'), value: 0.2 },
{ label: t('Small'), value: 0.3 },
{ label: t('Normal'), value: 0.4 },
{ label: t('Large'), value: 0.5 },
{ label: t('Huge'), value: 0.6 },
];
function makeFontSizeControl(
name: string,
label: string,
defaultValue: number,
options: { label: string; value: number }[],
): CustomControlItem {
return {
name,
config: {
type: 'SelectControl',
label: t(label),
renderTrigger: true,
clearable: false,
default: defaultValue,
options,
},
};
}
export const headerFontSize = makeFontSizeControl(
'header_font_size',
'Big Number Font Size',
0.4,
FONT_SIZE_OPTIONS_LARGE,
);
export const subtitleFontSize = makeFontSizeControl(
'subtitle_font_size',
'Subtitle Font Size',
0.15,
FONT_SIZE_OPTIONS_SMALL,
);
export const subheaderFontSize = makeFontSizeControl(
'subheader_font_size',
'Subheader Font Size',
0.15,
FONT_SIZE_OPTIONS_SMALL,
);
export const metricNameFontSize = makeFontSizeControl(
'metric_name_font_size',
'Metric Name Font Size',
0.15,
FONT_SIZE_OPTIONS_SMALL,
);
export const subtitleFontSize: CustomControlItem = {
name: 'subtitle_font_size',
config: {
type: 'SelectControl',
label: t('Subtitle Font Size'),
renderTrigger: true,
clearable: false,
default: 0.15,
// Values represent the percentage of space a subtitle should take
options: [
{
label: t('Tiny'),
value: 0.125,
},
{
label: t('Small'),
value: 0.15,
},
{
label: t('Normal'),
value: 0.2,
},
{
label: t('Large'),
value: 0.3,
},
{
label: t('Huge'),
value: 0.4,
},
],
},
};
export const subheaderFontSize: CustomControlItem = {
name: 'subheader_font_size',
config: {
type: 'SelectControl',
label: t('Subheader Font Size'),
renderTrigger: true,
clearable: false,
default: 0.15,
// Values represent the percentage of space a subheader should take
options: [
{
label: t('Tiny'),
value: 0.125,
},
{
label: t('Small'),
value: 0.15,
},
{
label: t('Normal'),
value: 0.2,
},
{
label: t('Large'),
value: 0.3,
},
{
label: t('Huge'),
value: 0.4,
},
],
},
};
export const subtitleControl: CustomControlItem = {
name: 'subtitle',
@@ -93,23 +131,3 @@ export const subtitleControl: CustomControlItem = {
description: t('Description text that shows up below your Big Number'),
},
};
export const showMetricNameControl: CustomControlItem = {
name: 'show_metric_name',
config: {
type: 'CheckboxControl',
label: t('Show Metric Name'),
renderTrigger: true,
default: false,
description: t('Whether to display the metric name'),
},
};
export const metricNameFontSizeWithVisibility: CustomControlItem = {
...metricNameFontSize,
config: {
...metricNameFontSize.config,
visibility: ({ controls }) => controls?.show_metric_name?.value === true,
resetOnHide: false,
},
};

View File

@@ -75,10 +75,6 @@ export type BigNumberVizProps = {
bigNumberFallback?: TimeSeriesDatum;
headerFormatter: ValueFormatter | TimeFormatter;
formatTime?: TimeFormatter;
metricName?: string;
friendlyMetricName?: string;
metricNameFontSize?: number;
showMetricName?: boolean;
headerFontSize: number;
kickerFontSize?: number;
subheader?: string;

View File

@@ -22,10 +22,6 @@ import utc from 'dayjs/plugin/utc';
import {
getTimeFormatter,
getTimeFormatterForGranularity,
isAdhocMetricSimple,
isSavedMetric,
Metric,
QueryFormMetric,
SMART_DATE_ID,
TimeGranularity,
} from '@superset-ui/core';
@@ -51,43 +47,3 @@ export const getDateFormatter = (
timeFormat === SMART_DATE_ID
? getTimeFormatterForGranularity(granularity)
: getTimeFormatter(timeFormat ?? fallbackFormat);
export function getOriginalLabel(
metric: QueryFormMetric,
metrics: Metric[] = [],
): string {
const metricLabel = typeof metric === 'string' ? metric : metric.label || '';
if (isSavedMetric(metric)) {
const metricEntry = metrics.find(m => m.metric_name === metric);
return (
metricEntry?.verbose_name ||
metricEntry?.metric_name ||
metric ||
'Unknown Metric'
);
}
if (isAdhocMetricSimple(metric)) {
const column = metric.column || {};
const columnName = column.column_name || 'unknown_column';
const verboseName = column.verbose_name || columnName;
const aggregate = metric.aggregate || 'UNKNOWN';
return metric.hasCustomLabel && metric.label
? metric.label
: `${aggregate}(${verboseName})`;
}
if (
typeof metric === 'object' &&
'expressionType' in metric &&
metric.expressionType === 'SQL' &&
'sqlExpression' in metric
) {
return metric.hasCustomLabel && metric.label
? metric.label
: metricLabel || 'Custom Metric';
}
return metricLabel || 'Unknown Metric';
}

View File

@@ -32,7 +32,6 @@ export const DEFAULT_FORM_DATA: Partial<EchartsBubbleFormData> = {
xAxisBounds: [null, null],
yAxisBounds: [null, null],
xAxisLabelRotation: defaultXAxis.xAxisLabelRotation,
xAxisLabelInterval: defaultXAxis.xAxisLabelInterval,
opacity: 0.6,
};

View File

@@ -31,7 +31,6 @@ import {
truncateXAxis,
xAxisBounds,
xAxisLabelRotation,
xAxisLabelInterval,
} from '../controls';
import { defaultYAxis } from '../defaults';
@@ -134,7 +133,6 @@ const config: ControlPanelConfig = {
},
],
[xAxisLabelRotation],
[xAxisLabelInterval],
[
{
name: 'x_axis_title_margin',

View File

@@ -120,7 +120,6 @@ export default function transformProps(chartProps: EchartsBubbleChartProps) {
truncateXAxis,
truncateYAxis,
xAxisLabelRotation,
xAxisLabelInterval,
yAxisLabelRotation,
tooltipSizeFormat,
opacity,
@@ -198,7 +197,6 @@ export default function transformProps(chartProps: EchartsBubbleChartProps) {
},
},
nameRotate: xAxisLabelRotation,
interval: xAxisLabelInterval,
scale: true,
name: bubbleXAxisTitle,
nameLocation: 'middle',

View File

@@ -19,7 +19,6 @@
import { ensureIsArray, t } from '@superset-ui/core';
import { cloneDeep } from 'lodash';
import {
ControlPanelsContainerProps,
ControlPanelConfig,
ControlPanelSectionConfig,
ControlSetRow,
@@ -28,8 +27,6 @@ import {
getStandardizedControls,
sections,
sharedControls,
DEFAULT_SORT_SERIES_DATA,
SORT_SERIES_CHOICES,
} from '@superset-ui/chart-controls';
import { DEFAULT_FORM_DATA } from './types';
@@ -41,7 +38,6 @@ import {
truncateXAxis,
xAxisBounds,
xAxisLabelRotation,
xAxisLabelInterval,
} from '../controls';
const {
@@ -200,23 +196,6 @@ function createCustomizeSection(
},
},
],
[
{
name: `only_total${controlSuffix}`,
config: {
type: 'CheckboxControl',
label: t('Only Total'),
default: true,
renderTrigger: true,
description: t(
'Only show the total value on the stacked chart, and not show on the selected category',
),
visibility: ({ controls }: ControlPanelsContainerProps) =>
Boolean(controls?.show_value?.value) &&
Boolean(controls?.stack?.value),
},
},
],
[
{
name: `opacity${controlSuffix}`,
@@ -279,35 +258,6 @@ function createCustomizeSection(
},
},
],
[<ControlSubSectionHeader>{t('Series Order')}</ControlSubSectionHeader>],
[
{
name: `sort_series_type${controlSuffix}`,
config: {
type: 'SelectControl',
freeForm: false,
label: t('Sort Series By'),
choices: SORT_SERIES_CHOICES,
default: DEFAULT_SORT_SERIES_DATA.sort_series_type,
renderTrigger: true,
description: t(
'Based on what should series be ordered on the chart and legend',
),
},
},
],
[
{
name: `sort_series_ascending${controlSuffix}`,
config: {
type: 'CheckboxControl',
label: t('Sort Series Ascending'),
default: DEFAULT_SORT_SERIES_DATA.sort_series_ascending,
renderTrigger: true,
description: t('Sort series in ascending order'),
},
},
],
];
}
@@ -369,7 +319,6 @@ const config: ControlPanelConfig = {
[<ControlSubSectionHeader>{t('X Axis')}</ControlSubSectionHeader>],
['x_axis_time_format'],
[xAxisLabelRotation],
[xAxisLabelInterval],
...richTooltipSection,
// eslint-disable-next-line react/jsx-key
[<ControlSubSectionHeader>{t('Y Axis')}</ControlSubSectionHeader>],

View File

@@ -97,7 +97,6 @@ import {
getXAxisFormatter,
getYAxisFormatter,
} from '../utils/formatters';
import { getMetricDisplayName } from '../utils/metricDisplayName';
const getFormatter = (
customFormatters: Record<string, ValueFormatter>,
@@ -173,8 +172,6 @@ export default function transformProps(
showLegend,
showValue,
showValueB,
onlyTotal,
onlyTotalB,
stack,
stackB,
truncateXAxis,
@@ -195,7 +192,6 @@ export default function transformProps(
tooltipSortByMetric,
xAxisBounds,
xAxisLabelRotation,
xAxisLabelInterval,
groupby,
groupbyB,
xAxis: xAxisOrig,
@@ -206,10 +202,6 @@ export default function transformProps(
yAxisTitleMargin,
yAxisTitlePosition,
sliceId,
sortSeriesType,
sortSeriesTypeB,
sortSeriesAscending,
sortSeriesAscendingB,
timeGrainSqla,
percentageThreshold,
metrics = [],
@@ -230,42 +222,14 @@ export default function transformProps(
}
const rebasedDataA = rebaseForecastDatum(data1, verboseMap);
const { totalStackedValues, thresholdValues } = extractDataTotalValues(
rebasedDataA,
{
stack,
percentageThreshold,
xAxisCol: xAxisLabel,
},
);
const MetricDisplayNameA = getMetricDisplayName(metrics[0], verboseMap);
const MetricDisplayNameB = getMetricDisplayName(metricsB[0], verboseMap);
const [rawSeriesA, sortedTotalValuesA] = extractSeries(rebasedDataA, {
const [rawSeriesA] = extractSeries(rebasedDataA, {
fillNeighborValue: stack ? 0 : undefined,
xAxis: xAxisLabel,
sortSeriesType,
sortSeriesAscending,
stack,
totalStackedValues,
});
const rebasedDataB = rebaseForecastDatum(data2, verboseMap);
const {
totalStackedValues: totalStackedValuesB,
thresholdValues: thresholdValuesB,
} = extractDataTotalValues(rebasedDataB, {
stack: Boolean(stackB),
percentageThreshold,
xAxisCol: xAxisLabel,
});
const [rawSeriesB, sortedTotalValuesB] = extractSeries(rebasedDataB, {
const [rawSeriesB] = extractSeries(rebasedDataB, {
fillNeighborValue: stackB ? 0 : undefined,
xAxis: xAxisLabel,
sortSeriesType: sortSeriesTypeB,
sortSeriesAscending: sortSeriesAscendingB,
stack: Boolean(stackB),
totalStackedValues: totalStackedValuesB,
});
const dataTypes = getColtypesMapping(queriesData[0]);
@@ -323,11 +287,25 @@ export default function transformProps(
);
const showValueIndexesA = extractShowValueIndexes(rawSeriesA, {
stack,
onlyTotal,
});
const showValueIndexesB = extractShowValueIndexes(rawSeriesB, {
stack,
onlyTotal,
});
const { totalStackedValues, thresholdValues } = extractDataTotalValues(
rebasedDataA,
{
stack,
percentageThreshold,
xAxisCol: xAxisLabel,
},
);
const {
totalStackedValues: totalStackedValuesB,
thresholdValues: thresholdValuesB,
} = extractDataTotalValues(rebasedDataB, {
stack: Boolean(stackB),
percentageThreshold,
xAxisCol: xAxisLabel,
});
annotationLayers
@@ -395,12 +373,6 @@ export default function transformProps(
const seriesName = inverted[entryName] || entryName;
const colorScaleKey = getOriginalSeries(seriesName, array);
let displayName = `${entryName} (Query A)`;
if (groupby.length > 0) {
displayName = `${MetricDisplayNameA} (Query A), ${entryName}`;
}
const seriesFormatter = getFormatter(
customFormatters,
formatter,
@@ -410,10 +382,7 @@ export default function transformProps(
);
const transformedSeries = transformSeries(
{
...entry,
id: `${displayName || ''}`,
},
entry,
colorScale,
colorScaleKey,
{
@@ -423,7 +392,6 @@ export default function transformProps(
areaOpacity: opacity,
seriesType,
showValue,
onlyTotal,
stack: Boolean(stack),
stackIdSuffix: '\na',
yAxisIndex,
@@ -438,8 +406,8 @@ export default function transformProps(
formatter: seriesFormatter,
})
: seriesFormatter,
totalStackedValues: sortedTotalValuesA,
showValueIndexes: showValueIndexesA,
totalStackedValues,
thresholdValues,
timeShiftColor,
},
@@ -453,12 +421,6 @@ export default function transformProps(
const seriesName = `${seriesEntry} (1)`;
const colorScaleKey = getOriginalSeries(seriesEntry, array);
let displayName = `${entryName} (Query B)`;
if (groupbyB.length > 0) {
displayName = `${MetricDisplayNameB} (Query B), ${entryName}`;
}
const seriesFormatter = getFormatter(
customFormattersSecondary,
formatterSecondary,
@@ -468,11 +430,7 @@ export default function transformProps(
);
const transformedSeries = transformSeries(
{
...entry,
id: `${displayName || ''}`,
},
entry,
colorScale,
colorScaleKey,
{
@@ -482,12 +440,13 @@ export default function transformProps(
areaOpacity: opacityB,
seriesType: seriesTypeB,
showValue: showValueB,
onlyTotal: onlyTotalB,
stack: Boolean(stackB),
stackIdSuffix: '\nb',
yAxisIndex: yAxisIndexB,
filterState,
seriesKey: entry.name,
seriesKey: primarySeries.has(entry.name as string)
? `${entry.name} (1)`
: entry.name,
sliceId,
queryIndex: 1,
formatter:
@@ -497,8 +456,8 @@ export default function transformProps(
formatter: seriesFormatter,
})
: seriesFormatter,
totalStackedValues: sortedTotalValuesB,
showValueIndexes: showValueIndexesB,
totalStackedValues: totalStackedValuesB,
thresholdValues: thresholdValuesB,
timeShiftColor,
},
@@ -555,7 +514,6 @@ export default function transformProps(
axisLabel: {
formatter: xAxisFormatter,
rotate: xAxisLabelRotation,
interval: xAxisLabelInterval,
},
minorTick: { show: minorTicks },
minInterval:

View File

@@ -61,7 +61,6 @@ export type EchartsMixedTimeseriesFormData = QueryFormData & {
zoomable: boolean;
richTooltip: boolean;
xAxisLabelRotation: number;
xAxisLabelInterval?: number | string;
colorScheme?: string;
// types specific to Query A and Query B
area: boolean;
@@ -134,7 +133,6 @@ export const DEFAULT_FORM_DATA: EchartsMixedTimeseriesFormData = {
zoomable: TIMESERIES_DEFAULTS.zoomable,
richTooltip: TIMESERIES_DEFAULTS.richTooltip,
xAxisLabelRotation: TIMESERIES_DEFAULTS.xAxisLabelRotation,
xAxisLabelInterval: TIMESERIES_DEFAULTS.xAxisLabelInterval,
...DEFAULT_TITLE_FORM_DATA,
};

View File

@@ -16,30 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
import {
buildQueryContext,
getMetricLabel,
QueryFormData,
} from '@superset-ui/core';
import { getContributionLabel } from './utils';
import { buildQueryContext, QueryFormData } from '@superset-ui/core';
export default function buildQuery(formData: QueryFormData) {
const { metric, sort_by_metric } = formData;
const metricLabel = getMetricLabel(metric);
return buildQueryContext(formData, baseQueryObject => [
{
...baseQueryObject,
...(sort_by_metric && { orderby: [[metric, false]] }),
post_processing: [
{
operation: 'contribution',
options: {
columns: [metricLabel],
rename_columns: [getContributionLabel(metricLabel)],
},
},
],
},
]);
}

View File

@@ -1,19 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export const CONTRIBUTION_SUFFIX = '__contribution' as const;

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