Merge branch 'master' into hotfix/lnglat-and-delimited

This commit is contained in:
RoXoM 2024-03-11 22:15:07 +08:00
commit 578113f3a6
269 changed files with 396568 additions and 373564 deletions

View File

@ -73,14 +73,6 @@ github:
- test-postgres (3.9)
- test-postgres (3.10)
- test-sqlite (3.9)
- docker-build (dev, linux/amd64)
- docker-build (lean, linux/amd64)
- docker-build (py310, linux/arm64)
- docker-build (py310, linux/amd64)
- docker-build (websocket, linux/arm64)
- docker-build (websocket, linux/amd64)
- docker-build (dockerize, linux/arm64)
- docker-build (dockerize, linux/amd64)
required_pull_request_reviews:
dismiss_stale_reviews: false

View File

@ -4,7 +4,8 @@ on:
paths:
- "superset/migrations/**"
branches:
- 'master'
- "master"
- "[0-9].[0-9]"
pull_request:
paths:
- "superset/migrations/**"

View File

@ -1,72 +0,0 @@
# .github/workflows/chromatic.yml
# see https://www.chromatic.com/docs/github-actions
#
# 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.
#
# Workflow name
name: 'Chromatic Storybook Master'
# Event for the workflow
# Only run if changes were made in superset-frontend folder of repo on merge to Master
on:
# This will trigger when a branch merges to master when the PR has changes in the frontend folder updating the chromatic baseline
push:
branches:
- master
paths:
- "superset-frontend/**"
# List of jobs
jobs:
config:
runs-on: "ubuntu-latest"
outputs:
has-secrets: ${{ steps.check.outputs.has-secrets }}
steps:
- name: "Check for secrets"
id: check
shell: bash
run: |
if [ -n "${{ (secrets.CHROMATIC_PROJECT_TOKEN != '') || '' }}" ]; then
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
fi
chromatic-deployment:
needs: config
if: needs.config.outputs.has-secrets
# Operating System
runs-on: ubuntu-latest
# Job steps
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 👈 Required to retrieve git history
- name: Install dependencies
run: npm ci
working-directory: superset-frontend
# 👇 Build and publish Storybook to Chromatic
- name: Build and publish Storybook to Chromatic
id: chromatic-master
uses: chromaui/action@v10
# Required options for the Chromatic GitHub Action
with:
# 👇 Location of package.json from root of mono-repo
workingDir: superset-frontend
# 👇 Chromatic projectToken, refer to the manage page to obtain it.
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
exitZeroOnChanges: true # 👈 Option to prevent the workflow from failing
autoAcceptChanges: true # 👈 Option to accept all changes when merging to master

View File

@ -2,16 +2,16 @@ name: "CodeQL"
on:
push:
branches: [ "master" ]
branches: ["master", "[0-9].[0-9]"]
paths:
- 'superset/**'
- "superset/**"
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
branches: ["master"]
paths:
- 'superset/**'
- "superset/**"
schedule:
- cron: '0 4 * * *'
- cron: "0 4 * * *"
# cancel previous workflow jobs for PRs
concurrency:
@ -30,7 +30,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'python', 'javascript' ]
language: ["python", "javascript"]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:

View File

@ -3,29 +3,35 @@ name: Docker
on:
push:
branches:
- 'master'
- "master"
- "[0-9].[0-9]"
pull_request:
types: [synchronize, opened, reopened, ready_for_review]
branches:
- "master"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
setup_matrix:
runs-on: ubuntu-latest
outputs:
matrix_config: ${{ steps.set_matrix.outputs.matrix_config }}
steps:
- id: set_matrix
run: |
MATRIX_CONFIG=$(if [ "${{ github.event_name }}" == "pull_request" ]; then echo '["ci"]'; else echo '["dev", "lean", "py310", "websocket", "dockerize"]'; fi)
echo "matrix_config=${MATRIX_CONFIG}" >> $GITHUB_OUTPUT
echo $GITHUB_OUTPUT
docker-build:
name: docker-build
needs: setup_matrix
runs-on: ubuntu-latest
strategy:
matrix:
build_preset: ["dev", "lean", "py310", "websocket", "dockerize"]
platform: ["linux/amd64", "linux/arm64"]
exclude:
# disabling because slow! no python wheels for arm/py39 and
# QEMU is slow!
- build_preset: "dev"
platform: "linux/arm64"
- build_preset: "lean"
platform: "linux/arm64"
build_preset: ${{fromJson(needs.setup_matrix.outputs.matrix_config)}}
fail-fast: false
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
@ -46,4 +52,17 @@ jobs:
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
pip install click
./scripts/build_docker.py ${{ matrix.build_preset }} ${{ github.event_name }} --platform ${{ matrix.platform }}
if [ "${{ github.event_name }}" = "push" ]; then
./scripts/build_docker.py \
${{ matrix.build_preset }} \
${{ github.event_name }} \
--build_context_ref "$RELEASE" $FORCE_LATEST \
--platform "linux/arm64" \
--platform "linux/amd64"
elif [ "${{ github.event_name }}" = "pull_request" ]; then
./scripts/build_docker.py \
${{ matrix.build_preset }} \
${{ github.event_name }} \
--build_context_ref "$RELEASE" $FORCE_LATEST \
--platform "linux/amd64"
fi

View File

@ -3,7 +3,8 @@ name: Embedded SDK Release
on:
push:
branches:
- 'master'
- "master"
- "[0-9].[0-9]"
jobs:
config:
@ -31,7 +32,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: "16"
registry-url: 'https://registry.npmjs.org'
registry-url: "https://registry.npmjs.org"
- run: npm ci
- run: npm run ci:release
env:

View File

@ -4,6 +4,7 @@ on:
push:
branches:
- "master"
- "[0-9].[0-9]"
jobs:
config:
@ -33,8 +34,8 @@ jobs:
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '11'
distribution: "temurin"
java-version: "11"
- name: Generate fossa report
env:
FOSSA_API_KEY: ${{ secrets.FOSSA_API_KEY }}

View File

@ -19,7 +19,7 @@
# - Ensure that the job names in this workflow match exactly the names of the corresponding jobs in the main workflows.
# - This workflow should be kept as-is, without path-specific conditions.
name: No Operation Checks
name: no-op Checks
on: pull_request
jobs:

View File

@ -3,7 +3,8 @@ name: pre-commit checks
on:
push:
branches:
- 'master'
- "master"
- "[0-9].[0-9]"
pull_request:
types: [synchronize, opened, reopened, ready_for_review]

View File

@ -3,7 +3,8 @@ name: Prefer TypeScript
on:
push:
branches:
- 'master'
- "master"
- "[0-9].[0-9]"
paths:
- "superset-frontend/src/**"
pull_request:

View File

@ -3,7 +3,8 @@ name: release-workflow
on:
push:
branches:
- 'master'
- "master"
- "[0-9].[0-9]"
jobs:
config:

View File

@ -3,7 +3,8 @@ name: Superset CLI tests
on:
push:
branches:
- 'master'
- "master"
- "[0-9].[0-9]"
pull_request:
types: [synchronize, opened, reopened, ready_for_review]
@ -55,8 +56,8 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: 'requirements/testing.txt'
cache: "pip"
cache-dependency-path: "requirements/testing.txt"
- name: Install dependencies
if: steps.check.outcome == 'failure'
uses: ./.github/actions/cached-dependencies

View File

@ -6,6 +6,7 @@ on:
- "docs/**"
branches:
- "master"
- "[0-9].[0-9]"
jobs:
config:
@ -38,7 +39,7 @@ jobs:
- name: Set up Node.js 16
uses: actions/setup-node@v4
with:
node-version: '16'
node-version: "16"
- name: yarn install
run: |
yarn install --check-cache

View File

@ -3,7 +3,8 @@ name: E2E
on:
push:
branches:
- 'master'
- "master"
- "[0-9].[0-9]"
pull_request:
types: [synchronize, opened, reopened, ready_for_review]

View File

@ -4,6 +4,7 @@ on:
push:
branches:
- "master"
- "[0-9].[0-9]"
paths:
- "superset-frontend/**"
pull_request:

View File

@ -4,6 +4,7 @@ on:
push:
branches:
- "master"
- "[0-9].[0-9]"
paths:
- "helm/**"

View File

@ -4,7 +4,8 @@ name: Python-Integration
on:
push:
branches:
- 'master'
- "master"
- "[0-9].[0-9]"
pull_request:
types: [synchronize, opened, reopened, ready_for_review]
@ -54,8 +55,8 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: 'requirements/testing.txt'
cache: "pip"
cache-dependency-path: "requirements/testing.txt"
- name: Install dependencies
if: steps.check.outcome == 'failure'
uses: ./.github/actions/cached-dependencies
@ -120,8 +121,8 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: 'requirements/testing.txt'
cache: "pip"
cache-dependency-path: "requirements/testing.txt"
- name: Install dependencies
if: steps.check.outcome == 'failure'
uses: ./.github/actions/cached-dependencies
@ -180,8 +181,8 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: 'requirements/testing.txt'
cache: "pip"
cache-dependency-path: "requirements/testing.txt"
- name: Install dependencies
if: steps.check.outcome == 'failure'
uses: ./.github/actions/cached-dependencies

View File

@ -4,7 +4,8 @@ name: Python Misc
on:
push:
branches:
- 'master'
- "master"
- "[0-9].[0-9]"
paths:
- "superset/**"
pull_request:

View File

@ -4,7 +4,8 @@ name: Python Presto/Hive
on:
push:
branches:
- 'master'
- "master"
- "[0-9].[0-9]"
paths:
- "superset/**"
pull_request:
@ -70,8 +71,8 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: 'requirements/testing.txt'
cache: "pip"
cache-dependency-path: "requirements/testing.txt"
- name: Install dependencies
if: steps.check.outcome == 'failure'
uses: ./.github/actions/cached-dependencies
@ -147,8 +148,8 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: 'requirements/testing.txt'
cache: "pip"
cache-dependency-path: "requirements/testing.txt"
- name: Install dependencies
if: steps.check.outcome == 'failure'
uses: ./.github/actions/cached-dependencies

View File

@ -4,17 +4,20 @@ name: Python-Unit
on:
push:
branches:
- 'master'
- "master"
- "[0-9].[0-9]"
paths:
- "superset/**"
- "requirements/**"
- "tests/unit_tests/**"
- "scripts/**"
pull_request:
types: [synchronize, opened, reopened, ready_for_review]
paths:
- "superset/**"
- "requirements/**"
- "tests/unit_tests/**"
- "scripts/**"
# cancel previous workflow jobs for PRs
concurrency:
@ -47,9 +50,9 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: 'requirements/testing.txt'
# TODO: separated requirements.txt file just for unit tests
cache: "pip"
cache-dependency-path: "requirements/testing.txt"
# TODO: separated requirements.txt file just for unit tests
- name: Install dependencies
if: steps.check.outcome == 'failure'
uses: ./.github/actions/cached-dependencies

View File

@ -3,7 +3,8 @@ name: Translations
on:
push:
branches:
- 'master'
- "master"
- "[0-9].[0-9]"
pull_request:
types: [synchronize, opened, reopened, ready_for_review]
@ -24,7 +25,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '16'
node-version: "16"
- name: Install dependencies
uses: ./.github/actions/cached-dependencies
with:

View File

@ -2,7 +2,8 @@ name: WebSocket server
on:
push:
branches:
- 'master'
- "master"
- "[0-9].[0-9]"
paths:
- "superset-websocket/**"
pull_request:

View File

@ -4,6 +4,7 @@ on:
push:
branches:
- master
- "[0-9].[0-9]"
jobs:
config:
@ -31,7 +32,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '16'
node-version: "16"
- name: Install Dependencies
run: npm install
@ -39,7 +40,7 @@ jobs:
- name: Run Script
env:
SPREADSHEET_ID: '1oABNnzxJYzwUrHjr_c9wfYEq9dFL1ScVof9LlaAdxvo'
SPREADSHEET_ID: "1oABNnzxJYzwUrHjr_c9wfYEq9dFL1ScVof9LlaAdxvo"
SERVICE_ACCOUNT_KEY: ${{ secrets.GSHEET_KEY }}
run: npm run lint-stats
continue-on-error: true

View File

@ -201,5 +201,15 @@ Understanding the Superset Points of View
- [Superset API](https://superset.apache.org/docs/rest-api)
## Repo Activity
<a href="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats?repo_id=39464018" target="_blank" style="display: block" align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats/thumbnail.png?repo_id=39464018&image_size=auto&color_scheme=dark" width="655" height="auto">
<img alt="Performance Stats of apache/superset - Last 28 days" src="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats/thumbnail.png?repo_id=39464018&image_size=auto&color_scheme=light" width="655" height="auto">
</picture>
</a>
<!-- Made with [OSS Insight](https://ossinsight.io/) -->
<!-- telemetry/analytics pixel: -->
<img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=bc1c90cd-bc04-4e11-8c7b-289fb2839492" />

View File

@ -39,7 +39,7 @@ PYTHON=$(get_python_command)
PIP=$(get_pip_command)
# Get the release directory's path. If you unzip an Apache release and just run the npm script to validate the release, this will be a file name like `apache-superset-x.x.xrcx-source.tar.gz`
RELEASE_DIR_NAME="../../$(basename "$(dirname "$(pwd)")").tar.gz"
RELEASE_ZIP_PATH="../../$(basename "$(dirname "$(pwd)")")-source.tar.gz"
# Install dependencies from requirements.txt if the file exists
if [ -f "path/to/requirements.txt" ]; then
@ -47,8 +47,5 @@ if [ -f "path/to/requirements.txt" ]; then
$PYTHON -m $PIP install -r path/to/requirements.txt
fi
# echo $PYTHON
# echo $RELEASE_DIR_NAME
# Run the Python script with the parent directory name as an argument
$PYTHON ../RELEASING/verify_release.py "$RELEASE_DIR_NAME"
$PYTHON ../RELEASING/verify_release.py "$RELEASE_ZIP_PATH"

View File

@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
x-superset-image: &superset-image apachesuperset.docker.scarf.sh/apache/superset:${TAG:-latest-dev}
x-superset-image: &superset-image apachesuperset.docker.scarf.sh/apache/superset:${TAG:-master-dev}
x-superset-user: &superset-user root
x-superset-depends-on: &superset-depends-on
- db

View File

@ -9,7 +9,7 @@ version: 1
#### OS Dependencies
Make sure your machine meets the [OS dependencies](/docs/installation/installing-superset-from-scratch#os-dependencies) before following these steps.
Make sure your machine meets the [OS dependencies](/docs/installation/installing-superset-from-pypi#os-dependencies) before following these steps.
You also need to install MySQL or [MariaDB](https://mariadb.com/downloads).
Ensure that you are using Python version 3.9 or 3.10, then proceed with:

View File

@ -200,33 +200,6 @@ top of the box.
To exit, select any other part of the dashboard. Finally, dont forget to keep your changes using
**Save changes**.
### Filter Box
In this section, you will learn how to add a filter to your dashboard. Specifically, we will create
a filter that allows us to look at those flights that depart from a particular country.
A filter box visualization can be created as any other visualization by selecting **+ ‣ Chart**,
and then _tutorial_flights_ as the datasource and Filter Box as the visualization type.
First of all, in the **Time** section, remove the filter from the Time range selection by selecting
No filter.
Next, in **Filters Configurations** first add a new filter by selecting the plus sign and then edit
the newly created filter by selecting the pencil icon.
For our use case, it makes most sense to present a list of countries in alphabetical order. First,
enter the column as Origin Country and keep all other options the same and then select **Run
Query**. This gives us a preview of our filter.
Next, remove the date filter by unchecking the Date Filter checkbox.
<img src={useBaseUrl("/img/tutorial/filter_on_origin_country.png" )} />
Finally, select **Save**, name the chart as Tutorial Filter, add the chart to our existing Tutorial
Dashboard and then Save & go to dashboard. Once on the Dashboard, try using the filter to show only
those flights that departed from the United Kingdom you will see the filter is applied to all of
the other visualizations on the dashboard.
### Publishing Your Dashboard
If you have followed all of the steps outlined in the previous section, you should have a dashboard

View File

@ -64,7 +64,7 @@ DATA_CACHE_CONFIG = {
The cache timeout for charts may be overridden by the settings for an individual chart, dataset, or
database. Each of these configurations will be checked in order before falling back to the default
value defined in `DATA_CACHE_CONFIG.
value defined in `DATA_CACHE_CONFIG`.
Note, that by setting the cache timeout to `-1`, caching for charting data can be disabled, either
per chart, dataset or database, or by default if set in `DATA_CACHE_CONFIG`.

View File

@ -1,47 +1,47 @@
# Docker Images and Tags
# Docker builds, images and tags
The Apache Superset community extensively uses Docker for development, release,
and productionizing Superset. This page details our Docker builds and tag naming
schemes to help users navigate our offerings.
Images are built and pushed to the [Superset Docker Hub repository](
https://hub.docker.com/r/apache/superset). Different sets of images are created for:
https://hub.docker.com/r/apache/superset) using GitHub Actions.
Different sets of images are built and/or published at different times:
- **Published releases** (`release`): with tags like `3.0.0` and the `latest` tag.
Those are multi-platform (arm+amd). More on that later.
- **Pull request iterations** (`pull_request`):, each identified by tags starting with a SHA like
`8a2f7d378ab13c156fa183d9284b607ed69f5ecc`, and `pr-3454`, referencing the pull
request ID.
- **Published releases** (`release`): published using
tags like `3.0.0` and the `latest` tag.
- **Pull request iterations** (`pull_request`): for each pull request, while
we actively build the docker to validate the build, we do
not publish those images for security reasons, we simply `docker build --load`
- **Merges to the main branch** (`push`): resulting in new SHAs, with tags
prefixed with `master` for the latest `master` version.
Each CI build run has multiple builds for different purposes, identified by suffixes:
- **Build preset:** We offer various images for different needs:
- `lean`: The default Docker image, including both frontend and backend. Tags
without a build_preset are lean builds, e.g., `latest`.
- `dev`: For development, with a headless browser and root access.
- `py310`, e.g., Py310: Similar to lean but with a different Python version (in this example, 3.10).
- `ci`: For certain CI workloads.
- `websocket`: For Superset clusters supporting advanced features.
- `dockerize`: Used by Helm.
- **Platform:** We build for `linux/arm64` and `linux/amd64`. The `-arm` suffix
indicates ARM builds (e.g., `latest-arm`), while tags without a suffix are for
AMD (e.g., `latest`).
# Build presets
## Key Image Tags and Examples
We have a set of build "presets" that each represent a combination of
parameters for the build, mostly pointing to either different target layer
for the build, and/or base image.
- `latest`: The latest official release build, implicitly the lean build on
`linux/amd64`.
Here are the build presets that are exposed through the `build_docker.py` script:
- `lean`: The default Docker image, including both frontend and backend. Tags
without a build_preset are lean builds, e.g., `latest`.
- `dev`: For development, with a headless browser, dev-related utilities and root access.
- `py310`, e.g., Py310: Similar to lean but with a different Python version (in this example, 3.10).
- `ci`: For certain CI workloads.
- `websocket`: For Superset clusters supporting advanced features.
- `dockerize`: Used by Helm.
## Key tags examples
- `latest`: The latest official release build
- `latest-dev`: the `-dev` image of the latest official release build, with a
headless browser and root access.
- `master`: The latest build from the `master` branch, implicitly lean on
`linux/amd64`.
- `master`: The latest build from the `master` branch, implicitly the lean build
preset
- `master-dev`: Similar to `master` but includes a headless browser and root access.
- `pr-5252`: The latest commit in PR 5252.
- `30948dc401b40982cb7c0dbf6ebbe443b2748c1b-dev-arm`: A `linux/arm64` build for
this specific SHA, which could be from a pull request, master merge, or release.
- `30948dc-dev-arm`: Same as above, but SHA truncated to 7 characters for a
shorter handle on the same image
- `30948dc401b40982cb7c0dbf6ebbe443b2748c1b-dev`: A build for
this specific SHA, which could be from a `master` merge, or release.
- `websocket-latest`: The WebSocket image for use in a Superset cluster.
For insights or modifications to the build matrix and tagging conventions,
@ -64,9 +64,9 @@ build times, larger images, lower layer cache hit rate, ...).
For production use cases, we recommend that you derive our `lean` image(s) and
add database support for the database you need.
## On supporting arm64 AND amd64
## On supporting different platforms (namely arm64 AND amd64)
Only the release builds are multi-platform, supporting `linux/arm64`
Currently all automated builds are multi-platform, supporting both `linux/arm64`
and `linux/amd64`. This enables higher level constructs like `helm` and
docker-compose to point to these images and effectively be multi-platform
as well.

View File

@ -1,11 +1,13 @@
---
title: Installing From Scratch
title: Installing from PyPI
hide_title: true
sidebar_position: 2
version: 1
---
## Installing Superset from Scratch
## Installing Superset from PyPI
This page describes how to install Superset using the `apache-superset` package [published on PyPI](https://pypi.org/project/apache-superset/).
### OS Dependencies

View File

@ -174,7 +174,7 @@ In this section, we'll walkthrough the pre-defined Jinja macros in Superset.
**Current Username**
The `{{ current_username() }}` macro returns the username of the currently logged in user.
The `{{ current_username() }}` macro returns the `username` of the currently logged in user.
If you have caching enabled in your Superset configuration, then by default the `username` value will be used
by Superset when calculating the cache key. A cache key is a unique identifier that determines if there's a
@ -189,19 +189,34 @@ cache key by adding the following parameter to your Jinja code:
**Current User ID**
The `{{ current_user_id() }}` macro returns the user_id of the currently logged in user.
The `{{ current_user_id() }}` macro returns the account ID of the currently logged in user.
If you have caching enabled in your Superset configuration, then by default the `user_id` value will be used
If you have caching enabled in your Superset configuration, then by default the account `id` value 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.
You can disable the inclusion of the `user_id` value in the calculation of the
You can disable the inclusion of the account `id` value in the calculation of the
cache key by adding the following parameter to your Jinja code:
```
{{ current_user_id(add_to_cache_keys=False) }}
```
**Current User Email**
The `{{ current_user_email() }}` macro returns the email address of the currently logged in user.
If you have caching enabled in your Superset configuration, then by default the email address value 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.
You can disable the inclusion of the email value in the calculation of the
cache key by adding the following parameter to your Jinja code:
```
{{ current_user_email(add_to_cache_keys=False) }}
```
**Custom URL Parameters**
The `{{ url_param('custom_variable') }}` macro lets you define arbitrary URL

View File

@ -13,15 +13,11 @@ geospatial charts.
Here are a **few different ways you can get started with Superset**:
- Install Superset [from scratch](/docs/installation/installing-superset-from-scratch/)
- Deploy Superset locally with one command
[using Docker Compose](/docs/installation/installing-superset-using-docker-compose)
- Try a [Quickstart deployment](/docs/quickstart), which runs a single Docker container
- Install Superset [from PyPI](/docs/installation/installing-superset-from-pypi/)
- Deploy Superset [using Docker Compose](/docs/installation/installing-superset-using-docker-compose)
- Deploy Superset [with Kubernetes](/docs/installation/running-on-kubernetes)
- Run a [Docker image](https://hub.docker.com/r/apache/superset) from Dockerhub
- Download Superset [from Pypi here](https://pypi.org/project/apache-superset/)
- Install the latest version of Superset
[from GitHub](https://github.com/apache/superset/tree/latest)
- Download the [source from Apache Foundation's website](https://dist.apache.org/repos/dist/release/superset/)
- Download the [source code from Apache Foundation's website](https://dist.apache.org/repos/dist/release/superset/)
#### Video Overview

View File

@ -61,15 +61,39 @@ superset export_datasource_schema
As a reminder, you can use the `-b` flag to include back references.
### Importing Datasources from YAML
### Importing Datasources
In order to import datasources from a YAML file(s), run:
In order to import datasources from a ZIP file, run:
```
superset import_datasources -p <path / filename>
```
The optional username flag **-u** sets the user used for the datasource import. The default is 'admin'. Example:
```
superset import_datasources -p <path / filename> -u 'admin'
```
### Legacy Importing Datasources
#### From older versions of Superset to current version
When using Superset version 4.x.x to import from an older version (2.x.x or 3.x.x) importing is supported as the command `legacy_import_datasources` and expects a JSON or directory of JSONs. The options are `-r` for recursive and `-u` for specifying a user. Example of legacy import without options:
```
superset legacy_import_datasources -p <path or filename>
```
#### From older versions of Superset to older versions
When using an older Superset version (2.x.x & 3.x.x) of Superset, the command is `import_datasources`. ZIP and YAML files are supported and to switch between them the feature flag `VERSIONED_EXPORT` is used. When `VERSIONED_EXPORT` is `True`, `import_datasources` expects a ZIP file, otherwise YAML. Example:
```
superset import_datasources -p <path or filename>
```
If you supply a path all files ending with **yaml** or **yml** will be parsed. You can apply
When `VERSIONED_EXPORT` is `False`, if you supply a path all files ending with **yaml** or **yml** will be parsed. You can apply
additional flags (e.g. to search the supplied path recursively):
```

View File

@ -4,6 +4,16 @@ hide_title: true
sidebar_position: 2
---
#### Version 3.0.4, 3.1.1
| CVE | Title | Affected |
|:---------------|:-----------------------------------------------------------------------------|----------------------------:|
| CVE-2024-27315 | Improper error handling on alerts | < 3.0.4, >= 3.1.0, < 3.1.1 |
| CVE-2024-24773 | Improper validation of SQL statements allows for unauthorized access to data | < 3.0.4, >= 3.1.0, < 3.1.1 |
| CVE-2024-24772 | Improper Neutralisation of custom SQL on embedded context | < 3.0.4, >= 3.1.0, < 3.1.1 |
| CVE-2024-24779 | Improper data authorization when creating a new dataset | < 3.0.4, >= 3.1.0, < 3.1.1 |
| CVE-2024-26016 | Improper authorization validation on dashboards and charts import | < 3.0.4, >= 3.1.0, < 3.1.1 |
#### Version 3.0.3
| CVE | Title | Affected |

View File

@ -139,7 +139,7 @@ const config = {
({
docs: {
sidebarPath: require.resolve('./sidebars.js'),
editUrl: 'https://github.com/apache/superset/tree/master/docs',
editUrl: 'https://github.com/apache/superset/edit/master/docs',
},
blog: {
showReadingTime: true,

View File

@ -17,7 +17,7 @@
},
"dependencies": {
"@algolia/client-search": "^4.22.1",
"@ant-design/icons": "^4.7.0",
"@ant-design/icons": "^5.3.1",
"@docsearch/react": "^3.5.2",
"@docusaurus/core": "^2.4.1",
"@docusaurus/plugin-client-redirects": "^2.4.3",

View File

@ -259,3 +259,13 @@ a > span > svg {
height: 28px;
}
}
/* Edit Button */
.edit-page-link {
position: sticky;
bottom: 0px;
right: 0px;
border-radius: 10px;
background-color: #ccc;
}

View File

@ -0,0 +1,57 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import styled from '@emotion/styled';
import DocItem from '@theme-original/DocItem';
const EditPageLink = styled('a')`
position: fixed;
bottom: 20px;
right: 20px;
padding: 1rem;
padding-left: 4rem;
background-color: #444;
border-radius: 10px;
z-index: 9999;
background-image: url('/img/github-dark.png');
background-size: 2rem;
background-position: 1rem center;
background-repeat: no-repeat;
transition: background-color 0.3s; /* Smooth transition for hover effect */
bpx-shadow: 0 0 0 0 rgba(0,0,0,0); /* Smooth transition for hover effect */
scale: .9;
transition: all 0.3s;
&:hover {
background-color: #333;
box-shadow: 5px 5px 10px 0 rgba(0,0,0,0.3);
scale: 1;
}
`;
export default function DocItemWrapper(props) {
return (
<>
<EditPageLink href={props.content.metadata.editUrl} target="_blank" rel="noopener noreferrer">
Edit this page on GitHub
</EditPageLink>
<DocItem {...props} />
</>
);
}

View File

@ -21,3 +21,5 @@ RewriteRule ^(.*)$ https://superset.apache.org/$1 [R,L]
RewriteCond %{HTTP_HOST} ^superset.incubator.apache.org$ [NC]
RewriteRule ^(.*)$ https://superset.apache.org/$1 [R=301,L]
Header set Content-Security-Policy "default-src data: blob: 'self' *.apache.org *.bugherd.com *.scarf.sh *.googleapis.com *.googletagmanager.com *.google-analytics.com 'unsafe-inline' 'unsafe-eval'; frame-src *; frame-ancestors 'self' *.preset.io *.google.com https://sidebar.bugherd.com https://unpkg.com; form-action 'self'; worker-src blob:; img-src 'self' blob: data: https:; font-src 'self' https://fonts.gstatic.com; object-src 'none'"

View File

@ -153,10 +153,17 @@
dependencies:
"@ctrl/tinycolor" "^3.4.0"
"@ant-design/icons-svg@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz#8630da8eb4471a4aabdaed7d1ff6a97dcb2cf05a"
integrity sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw==
"@ant-design/colors@^7.0.0":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-7.0.2.tgz#c5c753a467ce8d86ba7ca4736d2c01f599bb5492"
integrity sha512-7KJkhTiPiLHSu+LmMJnehfJ6242OCxSlR3xHVBecYxnMW8MS/878NXct1GqYARyL59fyeFdKRxXTfvR9SnDgJg==
dependencies:
"@ctrl/tinycolor" "^3.6.1"
"@ant-design/icons-svg@^4.2.1", "@ant-design/icons-svg@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.4.0.tgz#435b544291fdabe992b3fd17829ff88c04c628b4"
integrity sha512-71rcNssTaRL1ytvPLebKuc/8Bjqxs5V1YkTbqlSCvNa0Se+HmYJwWHhRTpsSHBh+sWFtc7xpGCTRW2Ta04XyHw==
"@ant-design/icons@^4.7.0":
version "4.7.0"
@ -169,6 +176,17 @@
classnames "^2.2.6"
rc-util "^5.9.4"
"@ant-design/icons@^5.3.1":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-5.3.1.tgz#908eda82cbd455b83b30d620947ab8fbf2f4c5da"
integrity sha512-85zROTJCCApQn0Ee6L9561+Vd7yVKtSWNm2TpmOsYMrumchbzaRK83x1WWHv2VG+Y1ZAaKkDwcnnSPS/eSwNHA==
dependencies:
"@ant-design/colors" "^7.0.0"
"@ant-design/icons-svg" "^4.4.0"
"@babel/runtime" "^7.11.2"
classnames "^2.2.6"
rc-util "^5.31.1"
"@ant-design/react-slick@~0.28.1":
version "0.28.4"
resolved "https://registry.yarnpkg.com/@ant-design/react-slick/-/react-slick-0.28.4.tgz#8b296b87ad7c7ae877f2a527b81b7eebd9dd29a9"
@ -1505,26 +1523,12 @@
core-js-pure "^3.25.1"
regenerator-runtime "^0.13.11"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.15.4", "@babel/runtime@^7.3.1", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.16.3"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz"
integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@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.15.4", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.23.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7"
integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.18.6":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
dependencies:
regenerator-runtime "^0.13.11"
regenerator-runtime "^0.14.0"
"@babel/template@^7.12.7", "@babel/template@^7.16.0":
version "7.16.0"
@ -1634,6 +1638,11 @@
resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz#c3c5ae543c897caa9c2a68630bed355be5f9990f"
integrity sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==
"@ctrl/tinycolor@^3.6.1":
version "3.6.1"
resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz#b6c75a56a1947cc916ea058772d666a2c8932f31"
integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==
"@docsearch/css@3.5.2":
version "3.5.2"
resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.5.2.tgz#610f47b48814ca94041df969d9fcc47b91fc5aac"
@ -4887,16 +4896,17 @@ es-module-lexer@^1.2.1:
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.1.tgz#41ea21b43908fe6a287ffcbe4300f790555331f5"
integrity sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==
es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
version "0.10.53"
resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz"
integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==
es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.62, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
version "0.10.63"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.63.tgz#9c222a63b6a332ac80b1e373b426af723b895bd6"
integrity sha512-hUCZd2Byj/mNKjfP9jXrdVZ62B8KuA/VoK7X8nUh5qT+AxDmcbvZz041oDVZdbIN1qW6XY9VDNwzkvKnZvK2TQ==
dependencies:
es6-iterator "~2.0.3"
es6-symbol "~3.1.3"
next-tick "~1.0.0"
es6-iterator "^2.0.3"
es6-symbol "^3.1.3"
esniff "^2.0.1"
next-tick "^1.1.0"
es6-iterator@^2.0.3, es6-iterator@~2.0.3:
es6-iterator@^2.0.3:
version "2.0.3"
resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz"
integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
@ -4905,7 +4915,7 @@ es6-iterator@^2.0.3, es6-iterator@~2.0.3:
es5-ext "^0.10.35"
es6-symbol "^3.1.1"
es6-symbol@^3.1.1, es6-symbol@~3.1.3:
es6-symbol@^3.1.1, es6-symbol@^3.1.3:
version "3.1.3"
resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz"
integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==
@ -4956,6 +4966,16 @@ eslint-scope@5.1.1:
esrecurse "^4.3.0"
estraverse "^4.1.1"
esniff@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308"
integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==
dependencies:
d "^1.0.1"
es5-ext "^0.10.62"
event-emitter "^0.3.5"
type "^2.7.2"
esprima@^4.0.0:
version "4.0.1"
resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz"
@ -7367,11 +7387,6 @@ next-tick@1, next-tick@^1.1.0:
resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz"
integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
next-tick@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz"
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
no-case@^3.0.4:
version "3.0.4"
resolved "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz"
@ -8634,23 +8649,13 @@ rc-upload@~4.3.0:
classnames "^2.2.5"
rc-util "^5.2.0"
rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.0.7, rc-util@^5.12.0, rc-util@^5.14.0, rc-util@^5.15.0, rc-util@^5.16.1, rc-util@^5.2.0, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.4.0, rc-util@^5.5.0, rc-util@^5.5.1, rc-util@^5.6.1, rc-util@^5.7.0, rc-util@^5.8.0, rc-util@^5.9.4, rc-util@^5.9.8:
version "5.17.0"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.17.0.tgz#6b0788038075c3d5c215541539573a4a03827070"
integrity sha512-HWuTIKzBeZQQ7IBqdokE0wMp/xx39/KfUJ0gcquBigoldDCrf3YBcWFHrrQlJG7sI82Wg8mwp1uAKV3zMGfAgg==
rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.0.7, rc-util@^5.12.0, rc-util@^5.14.0, rc-util@^5.15.0, rc-util@^5.16.1, rc-util@^5.17.0, rc-util@^5.18.1, rc-util@^5.19.3, rc-util@^5.2.0, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.31.1, rc-util@^5.4.0, rc-util@^5.5.0, rc-util@^5.5.1, rc-util@^5.6.1, rc-util@^5.7.0, rc-util@^5.8.0, rc-util@^5.9.4, rc-util@^5.9.8:
version "5.38.1"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.38.1.tgz#4915503b89855f5c5cd9afd4c72a7a17568777bb"
integrity sha512-e4ZMs7q9XqwTuhIK7zBIVFltUtMSjphuPPQXHoHlzRzNdOwUxDejo0Zls5HYaJfRKNURcsS/ceKVULlhjBrxng==
dependencies:
"@babel/runtime" "^7.12.5"
react-is "^16.12.0"
shallowequal "^1.1.0"
rc-util@^5.17.0, rc-util@^5.18.1, rc-util@^5.19.3:
version "5.19.3"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.19.3.tgz#5f6aa854820f6d5824451d80771035b013eaf6d8"
integrity sha512-S28epi9E2s7Nir05q8Ffl3hzDLwkavTGi0PGH1cTqCmkpG1AeBEuZgQDpksYeU6IgHcds5hWIPE5PUcdFiZl8w==
dependencies:
"@babel/runtime" "^7.12.5"
react-is "^16.12.0"
shallowequal "^1.1.0"
"@babel/runtime" "^7.18.3"
react-is "^18.2.0"
rc-virtual-list@^3.2.0, rc-virtual-list@^3.4.1:
version "3.4.2"
@ -8785,7 +8790,7 @@ react-inspector@^5.1.1:
is-dom "^1.0.0"
prop-types "^15.0.0"
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -8795,6 +8800,11 @@ react-is@^17.0.2:
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-is@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
react-json-view@^1.21.3:
version "1.21.3"
resolved "https://registry.npmjs.org/react-json-view/-/react-json-view-1.21.3.tgz"
@ -8983,6 +8993,11 @@ regenerator-runtime@^0.13.4:
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
regenerator-runtime@^0.14.0:
version "0.14.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
regenerator-transform@^0.15.2:
version "0.15.2"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4"
@ -10076,6 +10091,11 @@ type@^2.5.0:
resolved "https://registry.npmjs.org/type/-/type-2.5.0.tgz"
integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==
type@^2.7.2:
version "2.7.2"
resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0"
integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==
typedarray-to-buffer@^3.1.5:
version "3.1.5"
resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz"

View File

@ -17,3 +17,6 @@
# under the License.
#
-e file:.
urllib3>=1.26.18
werkzeug>=3.0.1
numexpr>=2.9.0

View File

@ -1,4 +1,4 @@
# SHA1:a9dde048f1ee1f00586264d726d0e89f16e56183
# SHA1:85649679306ea016e401f37adfbad832028d2e5f
#
# This file is autogenerated by pip-compile-multi
# To update, run:
@ -78,7 +78,7 @@ cron-descriptor==1.2.24
# via apache-superset
croniter==1.0.15
# via apache-superset
cryptography==42.0.2
cryptography==42.0.4
# via
# apache-superset
# paramiko
@ -90,6 +90,8 @@ dnspython==2.1.0
# via email-validator
email-validator==1.1.3
# via flask-appbuilder
exceptiongroup==1.2.0
# via cattrs
flask==2.2.5
# via
# apache-superset
@ -104,7 +106,7 @@ flask==2.2.5
# flask-session
# flask-sqlalchemy
# flask-wtf
flask-appbuilder==4.4.0
flask-appbuilder==4.4.1
# via apache-superset
flask-babel==1.0.0
# via flask-appbuilder
@ -143,7 +145,9 @@ geopy==2.2.0
google-auth==2.27.0
# via shillelagh
greenlet==3.0.3
# via shillelagh
# via
# shillelagh
# sqlalchemy
gunicorn==21.2.0
# via apache-superset
hashids==1.3.1
@ -208,8 +212,10 @@ nh3==0.2.11
# via apache-superset
numba==0.57.1
# via pandas
numexpr==2.8.4
# via pandas
numexpr==2.9.0
# via
# -r requirements/base.in
# pandas
numpy==1.23.5
# via
# apache-superset
@ -232,7 +238,9 @@ packaging==23.1
pandas[performance]==2.0.3
# via apache-superset
paramiko==3.4.0
# via sshtunnel
# via
# apache-superset
# sshtunnel
parsedatetime==2.6
# via apache-superset
pgsanity==0.2.9
@ -293,7 +301,7 @@ pyyaml==6.0.1
# via
# apache-superset
# apispec
redis==4.5.4
redis==4.6.0
# via apache-superset
requests==2.31.0
# via
@ -347,6 +355,7 @@ tabulate==0.8.9
typing-extensions==4.4.0
# via
# apache-superset
# cattrs
# flask-limiter
# limits
# shillelagh
@ -358,6 +367,7 @@ url-normalize==1.4.3
# via requests-cache
urllib3==1.26.18
# via
# -r requirements/base.in
# requests
# requests-cache
# selenium
@ -370,6 +380,7 @@ wcwidth==0.2.5
# via prompt-toolkit
werkzeug==3.0.1
# via
# -r requirements/base.in
# flask
# flask-appbuilder
# flask-jwt-extended

View File

@ -10,8 +10,6 @@
# via
# -r requirements/base.in
# -r requirements/development.in
appnope==0.1.3
# via ipython
astroid==2.15.8
# via pylint
asttokens==2.2.1
@ -115,6 +113,8 @@ thrift==0.16.0
# thrift-sasl
thrift-sasl==0.4.3
# via pyhive
tomli==2.0.1
# via pylint
tomlkit==0.11.8
# via pylint
traitlets==5.9.0

View File

@ -15,6 +15,5 @@
# limitations under the License.
#
-r base.in
-e .[postgres]
gevent
-e .[postgres,gevent]
greenlet>=2.0.2

View File

@ -1,4 +1,4 @@
# SHA1:439e3ee196ce81f342c935117ba5e0eeee8c385b
# SHA1:f00a57c70a52607d638c19f64f426f887382927e
#
# This file is autogenerated by pip-compile-multi
# To update, run:
@ -11,7 +11,7 @@
# -r requirements/base.in
# -r requirements/docker.in
gevent==23.9.1
# via -r requirements/docker.in
# via apache-superset
psycopg2-binary==2.9.6
# via apache-superset
zope-event==4.5.0

View File

@ -52,6 +52,12 @@ pyproject-hooks==1.0.0
# via build
pyyaml==6.0.1
# via pre-commit
tomli==2.0.1
# via
# build
# pip-tools
# pyproject-api
# tox
toposort==1.10
# via pip-compile-multi
tox==4.6.4

View File

@ -20,6 +20,7 @@
docker
flask-testing
freezegun
grpcio>=1.55.3
openapi-spec-validator
parameterized
pyfakefs

View File

@ -1,4 +1,4 @@
# SHA1:95300275481abb1413eb98a5c79fb7cf96814cdd
# SHA1:a37a1037f359c1101162ef43288178fbf00c487d
#
# This file is autogenerated by pip-compile-multi
# To update, run:
@ -62,6 +62,7 @@ googleapis-common-protos==1.59.0
# grpcio-status
grpcio==1.60.1
# via
# -r requirements/testing.in
# google-api-core
# google-cloud-bigquery
# grpcio-status
@ -109,6 +110,8 @@ pyee==11.0.1
# via playwright
pyfakefs==5.2.2
# via -r requirements/testing.in
pyhive[presto]==0.7.0
# via apache-superset
pytest==7.3.1
# via
# -r requirements/testing.in
@ -132,7 +135,7 @@ tqdm==4.65.0
# via
# cmdstanpy
# prophet
trino==0.324.0
trino==0.328.0
# via apache-superset
tzlocal==4.3
# via trino

View File

@ -34,7 +34,7 @@ REGEXES=()
for CHECK in "$@"
do
if [[ ${CHECK} == "python" ]]; then
REGEX="(^\.github\/workflows\/.*python|^tests\/|^superset\/|^setup\.py|^requirements\/.+\.txt|^\.pylintrc)"
REGEX="(^\.github\/workflows\/.*python|^tests\/|^superset\/|^scripts\/|^setup\.py|^requirements\/.+\.txt|^\.pylintrc)"
echo "Searching for changes in python files"
elif [[ ${CHECK} == "frontend" ]]; then
REGEX="(^\.github\/workflows\/.*(bashlib|frontend|e2e)|^superset-frontend\/)"

View File

@ -98,13 +98,13 @@ done
if [ -z "${GITHUB_TAG_NAME}" ]; then
echo "Missing tag parameter, usage: ./scripts/tag_latest_release.sh <GITHUB_TAG_NAME>"
echo "::set-output name=SKIP_TAG::true"
echo "SKIP_TAG=true" >> $GITHUB_OUTPUT
exit 1
fi
if [ -z "$(git_show_ref)" ]; then
echo "The tag ${GITHUB_TAG_NAME} does not exist. Please use a different tag."
echo "::set-output name=SKIP_TAG::true"
echo "SKIP_TAG=true" >> $GITHUB_OUTPUT
exit 0
fi
@ -112,7 +112,7 @@ fi
if ! [[ ${GITHUB_TAG_NAME} =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
then
echo "This tag ${GITHUB_TAG_NAME} is not a valid release version. Not tagging."
echo "::set-output name=SKIP_TAG::true"
echo "SKIP_TAG=true" >> $GITHUB_OUTPUT
exit 1
fi
@ -177,14 +177,14 @@ done
# Determine the result based on the comparison
if [[ -z "$compare_result" ]]; then
echo "Versions are equal"
echo "::set-output name=SKIP_TAG::true"
echo "SKIP_TAG=true" >> $GITHUB_OUTPUT
elif [[ "$compare_result" == "greater" ]]; then
echo "This release tag ${GITHUB_TAG_NAME} is newer than the latest."
echo "::set-output name=SKIP_TAG::false"
echo "SKIP_TAG=false" >> $GITHUB_OUTPUT
# Add other actions you want to perform for a newer version
elif [[ "$compare_result" == "lesser" ]]; then
echo "This release tag ${GITHUB_TAG_NAME} is older than the latest."
echo "This release tag ${GITHUB_TAG_NAME} is not the latest. Not tagging."
# if you've gotten this far, then we don't want to run any tags in the next step
echo "::set-output name=SKIP_TAG::true"
echo "SKIP_TAG=true" >> $GITHUB_OUTPUT
fi

View File

@ -80,11 +80,10 @@ setup(
"colorama",
"croniter>=0.3.28",
"cron-descriptor",
# snowflake-connector-python as of 3.7.0 doesn't support >=42.* therefore lowering the min to 41.0.2
"cryptography>=41.0.2, <43.0.0",
"cryptography>=42.0.4, <43.0.0",
"deprecation>=2.1.0, <2.2.0",
"flask>=2.2.5, <3.0.0",
"flask-appbuilder>=4.4.0, <5.0.0",
"flask-appbuilder>=4.4.1, <5.0.0",
"flask-caching>=2.1.0, <3",
"flask-compress>=1.13, <2.0",
"flask-talisman>=1.0.0, <2.0",
@ -108,6 +107,7 @@ setup(
"packaging",
"pandas[performance]>=2.0.3, <2.1",
"parsedatetime",
"paramiko>=3.4.0",
"pgsanity",
"polyline>=2.0.0, <3.0",
"pyparsing>=3.0.6, <4",
@ -117,7 +117,7 @@ setup(
"pyarrow>=14.0.1, <15",
"pyyaml>=6.0.0, <7.0.0",
"PyJWT>=2.4.0, <3.0",
"redis>=4.5.4, <5.0",
"redis>=4.6.0, <5.0",
"selenium>=3.141.0, <4.10.0",
"shillelagh[gsheetsapi]>=1.2.10, <2.0",
"shortid",
@ -189,7 +189,7 @@ setup(
"playwright": ["playwright>=1.37.0, <2"],
"postgres": ["psycopg2-binary==2.9.6"],
"presto": ["pyhive[presto]>=0.6.5"],
"trino": ["trino>=0.324.0"],
"trino": ["trino>=0.328.0"],
"prophet": ["prophet>=1.1.5, <2"],
"redshift": ["sqlalchemy-redshift>=0.8.1, <0.9"],
"rockset": ["rockset-sqlalchemy>=0.0.1, <1"],

View File

@ -40,10 +40,15 @@ embedDashboard({
supersetDomain: "https://superset.example.com",
mountPoint: document.getElementById("my-superset-container"), // any html element that can contain an iframe
fetchGuestToken: () => fetchGuestTokenFromBackend(),
dashboardUiConfig: { // dashboard UI config: hideTitle, hideTab, hideChartControls, filters.visible, filters.expanded (optional)
dashboardUiConfig: { // dashboard UI config: hideTitle, hideTab, hideChartControls, filters.visible, filters.expanded (optional), urlParams (optional)
hideTitle: true,
filters: {
expanded: true,
},
urlParams: {
foo: 'value1',
bar: 'value2',
// ...
}
},
});

View File

@ -1,12 +1,12 @@
{
"name": "@superset-ui/embedded-sdk",
"version": "0.1.0-alpha.10",
"version": "0.1.0-alpha.11",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@superset-ui/embedded-sdk",
"version": "0.1.0-alpha.10",
"version": "0.1.0-alpha.11",
"license": "Apache-2.0",
"dependencies": {
"@superset-ui/switchboard": "^0.18.26-0",

View File

@ -1,6 +1,6 @@
{
"name": "@superset-ui/embedded-sdk",
"version": "0.1.0-alpha.10",
"version": "0.1.0-alpha.11",
"description": "SDK for embedding resources from Superset into your own application",
"access": "public",
"keywords": [

View File

@ -42,6 +42,9 @@ export type UiConfigType = {
visible?: boolean
expanded?: boolean
}
urlParams?: {
[key: string]: any
}
}
export type EmbedDashboardParams = {
@ -112,14 +115,15 @@ export async function embedDashboard({
async function mountIframe(): Promise<Switchboard> {
return new Promise(resolve => {
const iframe = document.createElement('iframe');
const dashboardConfig = dashboardUiConfig ? `?uiConfig=${calculateConfig()}` : ""
const dashboardConfigUrlParams = dashboardUiConfig ? {uiConfig: `${calculateConfig()}`} : undefined;
const filterConfig = dashboardUiConfig?.filters || {}
const filterConfigKeys = Object.keys(filterConfig)
const filterConfigUrlParams = filterConfigKeys.length > 0
? "&"
+ filterConfigKeys
.map(key => DASHBOARD_UI_FILTER_CONFIG_URL_PARAM_KEY[key] + '=' + filterConfig[key]).join('&')
: ""
const filterConfigUrlParams = Object.fromEntries(filterConfigKeys.map(
key => [DASHBOARD_UI_FILTER_CONFIG_URL_PARAM_KEY[key], filterConfig[key]]))
// Allow url query parameters from dashboardUiConfig.urlParams to override the ones from filterConfig
const urlParams = {...dashboardConfigUrlParams, ...filterConfigUrlParams, ...dashboardUiConfig?.urlParams}
const urlParamsString = Object.keys(urlParams).length ? '?' + new URLSearchParams(urlParams).toString() : ''
// set up the iframe's sandbox configuration
iframe.sandbox.add("allow-same-origin"); // needed for postMessage to work
@ -153,7 +157,7 @@ export async function embedDashboard({
resolve(new Switchboard({ port: ourPort, name: 'superset-embedded-sdk', debug }));
});
iframe.src = `${supersetDomain}/embedded/${id}${dashboardConfig}${filterConfigUrlParams}`;
iframe.src = `${supersetDomain}/embedded/${id}${urlParamsString}`;
//@ts-ignore
mountPoint.replaceChildren(iframe);
log('placed the iframe')

View File

@ -173,6 +173,13 @@ describe('Charts list', () => {
orderAlphabetical();
cy.getBySel('styled-card').first().contains('% Rural');
});
it('should preserve other filters when sorting', () => {
cy.getBySel('styled-card').should('have.length', 25);
setFilter('Type', 'Big Number');
setFilter('Sort', 'Least recently modified');
cy.getBySel('styled-card').should('have.length', 3);
});
});
describe('common actions', () => {

View File

@ -759,7 +759,7 @@ describe('Dashboard edit', () => {
cy.getBySel('dashboard-markdown-editor').click().type('Test resize');
resize(
'[data-test="dashboard-markdown-editor"] .resizable-container span div:last-child',
'[data-test="dashboard-markdown-editor"] .resizable-container div.resizable-container-handle--bottom + div',
).to(500, 600);
cy.getBySel('dashboard-markdown-editor').contains('Test resize');

View File

@ -117,6 +117,13 @@ describe('Dashboards list', () => {
orderAlphabetical();
cy.getBySel('styled-card').first().contains('Supported Charts Dashboard');
});
it('should preserve other filters when sorting', () => {
cy.getBySel('styled-card').should('have.length', 5);
setFilter('Status', 'Published');
setFilter('Sort', 'Least recently modified');
cy.getBySel('styled-card').should('have.length', 3);
});
});
describe('common actions', () => {

View File

@ -210,7 +210,7 @@ describe('Time range filter', () => {
.click()
.then(() => {
cy.get('.ant-radio-group').children().its('length').should('eq', 5);
cy.get('.ant-radio-checked + span').contains('last year');
cy.get('.ant-radio-checked + span').contains('Last year');
cy.get('[data-test=cancel-button]').click();
});
});

View File

@ -8068,9 +8068,9 @@
"integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
},
"node_modules/ip": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
"integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ=="
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
@ -10984,9 +10984,9 @@
}
},
"node_modules/socks/node_modules/ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ=="
},
"node_modules/sonic-boom": {
"version": "1.4.1",
@ -18391,9 +18391,9 @@
"integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
},
"ip": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
"integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ=="
},
"ipaddr.js": {
"version": "1.9.1",
@ -20518,9 +20518,9 @@
},
"dependencies": {
"ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ=="
}
}
},

File diff suppressed because it is too large Load Diff

View File

@ -43,7 +43,6 @@
"build-instrumented": "cross-env NODE_ENV=production BABEL_ENV=instrumented webpack --mode=production --color",
"build-storybook": "storybook build",
"check-translation": "prettier --check ../superset/translations/**/LC_MESSAGES/*.json",
"chromatic": "npx chromatic --skip 'dependabot/**' --only-changed",
"clean-translation": "prettier --write ../superset/translations/**/LC_MESSAGES/*.json",
"core:cover": "cross-env NODE_ENV=test jest --coverage --coverageThreshold='{\"global\":{\"statements\":100,\"branches\":100,\"functions\":100,\"lines\":100}}' --collectCoverageFrom='[\"packages/**/src/**/*.{js,ts}\", \"!packages/superset-ui-demo/**/*\"]' packages",
"cover": "cross-env NODE_ENV=test jest --coverage",
@ -57,7 +56,6 @@
"plugins:build": "node ./scripts/build.js",
"plugins:build-assets": "node ./scripts/copyAssets.js",
"plugins:build-storybook": "cd packages/superset-ui-demo && npm run build-storybook",
"plugins:chromatic": "cd packages/superset-ui-demo && npm run chromatic",
"plugins:create-conventional-version": "npm run prune && lerna version --conventional-commits --create-release github --no-private --yes",
"plugins:create-minor-version": "npm run prune && lerna version minor --no-private --yes",
"plugins:create-patch-version": "npm run prune && lerna version patch --no-private --yes",
@ -163,7 +161,7 @@
"polished": "^3.7.2",
"prop-types": "^15.7.2",
"query-string": "^6.13.7",
"re-resizable": "^6.6.1",
"re-resizable": "^6.9.11",
"react": "^16.13.1",
"react-ace": "^10.1.0",
"react-checkbox-tree": "^1.8.0",
@ -178,7 +176,7 @@
"react-js-cron": "^2.1.2",
"react-json-tree": "^0.17.0",
"react-jsonschema-form": "^1.8.1",
"react-lines-ellipsis": "^0.15.0",
"react-lines-ellipsis": "^0.15.4",
"react-loadable": "^5.5.0",
"react-redux": "^7.2.9",
"react-resize-detector": "^7.1.2",
@ -283,7 +281,6 @@
"babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-jsx-remove-data-test-id": "^2.1.3",
"babel-plugin-lodash": "^3.3.4",
"chromatic": "^6.7.4",
"copy-webpack-plugin": "^12.0.2",
"cross-env": "^5.2.1",
"css-loader": "^6.8.1",

View File

@ -37,3 +37,4 @@ export * from './math-expression';
export * from './ui-overrides';
export * from './hooks';
export * from './currency-format';
export * from './time-comparison';

View File

@ -115,11 +115,11 @@ export default function makeApi<
jsonPayload: undefined as JsonObject | undefined,
};
if (requestType === 'search') {
requestConfig.searchParams = payload as URLSearchParams;
requestConfig.searchParams = payload as unknown as URLSearchParams;
} else if (requestType === 'rison') {
requestConfig.endpoint = `${endpoint}?q=${rison.encode(payload)}`;
} else if (requestType === 'form') {
requestConfig.postPayload = payload as FormData;
requestConfig.postPayload = payload as unknown as FormData;
} else {
requestConfig.jsonPayload = payload as JsonObject;
}

View File

@ -69,6 +69,8 @@ export type QueryObjectExtras = Partial<{
time_grain_sqla?: TimeGranularity;
/** WHERE condition */
where?: string;
/** Instant Time Comparison */
instant_time_comparison_range?: string;
}>;
export type ResidualQueryObjectData = {

View File

@ -0,0 +1,47 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
## @superset-ui/core/time-comparison
This is a collection of methods used to support Time Comparison in charts.
#### Example usage
```js
import { getComparisonTimeRangeInfo } from '@superset-ui/core';
const { since, until } = getComparisonTimeRangeInfo(
adhocFilters,
extraFormData,
);
console.log(adhocFilters, extraFormData);
```
or
```js
import { ComparisonTimeRangeType } from '@superset-ui/core';
ComparisonTimeRangeType.Custom; // 'c'
ComparisonTimeRangeType.InheritRange; // 'r'
```
#### API
`fn(args)`
- Do something

View File

@ -0,0 +1,67 @@
/**
* 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 '../query';
import { AdhocFilter } from '../types';
/**
* This method is used to get the query filters to be applied to the comparison query after
* overriding the time range in case an extra form data is provided.
* For example when rendering a chart that uses time comparison in a dashboard with time filters.
* @param formData - the form data
* @param extraFormData - the extra form data
* @returns the query filters to be applied to the comparison query
*/
export const getComparisonFilters = (
formData: QueryFormData,
extraFormData: any,
): AdhocFilter[] => {
const timeFilterIndex: number =
formData.adhoc_filters?.findIndex(
filter => 'operator' in filter && filter.operator === 'TEMPORAL_RANGE',
) ?? -1;
const timeFilter: AdhocFilter | null =
timeFilterIndex !== -1 && formData.adhoc_filters
? formData.adhoc_filters[timeFilterIndex]
: null;
if (
timeFilter &&
'comparator' in timeFilter &&
typeof timeFilter.comparator === 'string'
) {
if (extraFormData?.time_range) {
timeFilter.comparator = extraFormData.time_range;
}
}
const comparisonQueryFilter = timeFilter ? [timeFilter] : [];
const otherFilters = formData.adhoc_filters?.filter(
(_value: any, index: number) => timeFilterIndex !== index,
);
const comparisonQueryFilters = otherFilters
? [...comparisonQueryFilter, ...otherFilters]
: comparisonQueryFilter;
return comparisonQueryFilters;
};
export default getComparisonFilters;

View File

@ -0,0 +1,65 @@
/**
* 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 '../query';
import { getComparisonFilters } from './getComparisonFilters';
import { ComparisonTimeRangeType } from './types';
/**
* This is the main function to get the comparison info. It will return the formData
* that a viz can use to query the comparison data and the time shift text needed for
* the comparison time range based on the control value.
* @param formData
* @param timeComparison
* @param extraFormData
* @returns the processed formData
*/
export const getComparisonInfo = (
formData: QueryFormData,
timeComparison: string,
extraFormData: any,
): QueryFormData => {
let comparisonFormData;
if (timeComparison !== ComparisonTimeRangeType.Custom) {
comparisonFormData = {
...formData,
adhoc_filters: getComparisonFilters(formData, extraFormData),
extra_form_data: {
...extraFormData,
time_range: undefined,
},
};
} else {
// This is when user selects Custom as time comparison
comparisonFormData = {
...formData,
adhoc_filters: formData.adhoc_custom,
extra_form_data: {
...extraFormData,
time_range: undefined,
},
};
}
return comparisonFormData;
};
export default getComparisonInfo;

View File

@ -0,0 +1,23 @@
/*
* 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 * from './types';
export { default as getComparisonInfo } from './getComparisonInfo';
export { default as getComparisonFilters } from './getComparisonFilters';

View File

@ -1,4 +1,4 @@
/**
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
@ -16,12 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
// eslint-disable-next-line import/prefer-default-export
export { default as PopKPIPlugin } from './plugin';
/**
* Note: this file exports the default export from PopKPI.tsx.
* If you want to export multiple visualization modules, you will need to
* either add additional plugin folders (similar in structure to ./plugin)
* OR export multiple instances of `ChartPlugin` extensions in ./plugin/index.ts
* which in turn load exports from CustomViz.tsx
* Supported comparison time ranges
*/
export enum ComparisonTimeRangeType {
Custom = 'c',
InheritedRange = 'r',
Month = 'm',
Week = 'w',
Year = 'y',
}

View File

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

View File

@ -0,0 +1,37 @@
/*
* 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 { ComparisonTimeRangeType } from '../time-comparison';
import { t } from '../translation';
import { ensureIsArray } from '../utils';
export const validateTimeComparisonRangeValues = (
timeRangeValue?: any,
controlValue?: any,
) => {
const isCustomTimeRange = timeRangeValue === ComparisonTimeRangeType.Custom;
const isCustomControlEmpty = controlValue?.every(
(val: any) => ensureIsArray(val).length === 0,
);
return isCustomTimeRange && isCustomControlEmpty
? [t('Filters for comparison must have a value')]
: [];
};
export default validateTimeComparisonRangeValues;

View File

@ -0,0 +1,144 @@
/*
* 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 { getComparisonFilters } from '@superset-ui/core';
const form_data = {
datasource: '22__table',
viz_type: 'pop_kpi',
slice_id: 97,
url_params: {
form_data_key:
'TaBakyDiAx2VsQ47gLmlsJKeN4foqnoxUKdbQrM05qnKMRjO9PDe42iZN1oxmxZ8',
save_action: 'overwrite',
slice_id: '97',
},
metrics: ['count'],
adhoc_filters: [
{
clause: 'WHERE',
comparator: '2004-02-16 : 2024-02-16',
datasourceWarning: false,
expressionType: 'SIMPLE',
filterOptionName: 'filter_8274fo9pogn_ihi8x28o7a',
isExtra: false,
isNew: false,
operator: 'TEMPORAL_RANGE',
sqlExpression: null,
subject: 'order_date',
} as any,
],
time_comparison: 'y',
adhoc_custom: [
{
clause: 'WHERE',
comparator: 'No filter',
expressionType: 'SIMPLE',
operator: 'TEMPORAL_RANGE',
subject: 'order_date',
},
],
row_limit: 10000,
y_axis_format: 'SMART_NUMBER',
header_font_size: 60,
subheader_font_size: 26,
comparison_color_enabled: true,
extra_form_data: {},
force: false,
result_format: 'json',
result_type: 'full',
};
const mockExtraFormData = {
time_range: 'new and cool range from extra form data',
};
describe('getComparisonFilters', () => {
it('Keeps the original adhoc_filters since no extra data was passed', () => {
const result = getComparisonFilters(form_data, {});
expect(result).toEqual(form_data.adhoc_filters);
});
it('Updates the time_range if the filter if extra form data is passed', () => {
const result = getComparisonFilters(form_data, mockExtraFormData);
const expectedFilters = [
{
clause: 'WHERE',
comparator: 'new and cool range from extra form data',
datasourceWarning: false,
expressionType: 'SIMPLE',
filterOptionName: 'filter_8274fo9pogn_ihi8x28o7a',
isExtra: false,
isNew: false,
operator: 'TEMPORAL_RANGE',
sqlExpression: null,
subject: 'order_date',
} as any,
];
expect(result.length).toEqual(1);
expect(result[0]).toEqual(expectedFilters[0]);
});
it('handles no time range filters', () => {
const result = getComparisonFilters(
{
...form_data,
adhoc_filters: [
{
expressionType: 'SIMPLE',
subject: 'address_line1',
operator: 'IN',
comparator: ['7734 Strong St.'],
clause: 'WHERE',
isExtra: false,
},
],
},
{},
);
const expectedFilters = [
{
expressionType: 'SIMPLE',
subject: 'address_line1',
operator: 'IN',
comparator: ['7734 Strong St.'],
clause: 'WHERE',
isExtra: false,
},
];
expect(result.length).toEqual(1);
expect(result[0]).toEqual(expectedFilters[0]);
});
it('If adhoc_filter is undefrined the code wont break', () => {
const result = getComparisonFilters(
{
...form_data,
adhoc_filters: undefined,
},
{},
);
expect(result).toEqual([]);
});
});

View File

@ -0,0 +1,174 @@
/*
* 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 { getComparisonInfo, ComparisonTimeRangeType } from '@superset-ui/core';
const form_data = {
datasource: '22__table',
viz_type: 'pop_kpi',
slice_id: 97,
url_params: {
form_data_key:
'TaBakyDiAx2VsQ47gLmlsJKeN4foqnoxUKdbQrM05qnKMRjO9PDe42iZN1oxmxZ8',
save_action: 'overwrite',
slice_id: '97',
},
metrics: ['count'],
adhoc_filters: [
{
clause: 'WHERE',
comparator: '2004-02-16 : 2024-02-16',
datasourceWarning: false,
expressionType: 'SIMPLE',
filterOptionName: 'filter_8274fo9pogn_ihi8x28o7a',
isExtra: false,
isNew: false,
operator: 'TEMPORAL_RANGE',
sqlExpression: null,
subject: 'order_date',
} as any,
],
time_comparison: 'y',
adhoc_custom: [
{
clause: 'WHERE',
comparator: 'No filter',
expressionType: 'SIMPLE',
operator: 'TEMPORAL_RANGE',
subject: 'order_date',
},
],
row_limit: 10000,
y_axis_format: 'SMART_NUMBER',
header_font_size: 60,
subheader_font_size: 26,
comparison_color_enabled: true,
extra_form_data: {},
force: false,
result_format: 'json',
result_type: 'full',
};
const mockExtraFormData = {
time_range: 'new and cool range from extra form data',
};
describe('getComparisonInfo', () => {
it('Keeps the original adhoc_filters since no extra data was passed', () => {
const resultFormData = getComparisonInfo(
form_data,
ComparisonTimeRangeType.Year,
{},
);
expect(resultFormData).toEqual(form_data);
});
it('Updates the time_range of the adhoc_filters when extra form data is passed', () => {
const resultFormData = getComparisonInfo(
form_data,
ComparisonTimeRangeType.Month,
mockExtraFormData,
);
const expectedFilters = [
{
clause: 'WHERE',
comparator: 'new and cool range from extra form data',
datasourceWarning: false,
expressionType: 'SIMPLE',
filterOptionName: 'filter_8274fo9pogn_ihi8x28o7a',
isExtra: false,
isNew: false,
operator: 'TEMPORAL_RANGE',
sqlExpression: null,
subject: 'order_date',
} as any,
];
expect(resultFormData.adhoc_filters?.length).toEqual(1);
expect(resultFormData.adhoc_filters).toEqual(expectedFilters);
});
it('handles no time range filters', () => {
const resultFormData = getComparisonInfo(
{
...form_data,
adhoc_filters: [
{
expressionType: 'SIMPLE',
subject: 'address_line1',
operator: 'IN',
comparator: ['7734 Strong St.'],
clause: 'WHERE',
isExtra: false,
},
],
},
ComparisonTimeRangeType.Week,
{},
);
const expectedFilters = [
{
expressionType: 'SIMPLE',
subject: 'address_line1',
operator: 'IN',
comparator: ['7734 Strong St.'],
clause: 'WHERE',
isExtra: false,
},
];
expect(resultFormData.adhoc_filters?.length).toEqual(1);
expect(resultFormData.adhoc_filters?.[0]).toEqual(expectedFilters[0]);
});
it('If adhoc_filter is undefrined the code wont break', () => {
const resultFormData = getComparisonInfo(
{
...form_data,
adhoc_filters: undefined,
},
ComparisonTimeRangeType.InheritedRange,
{},
);
expect(resultFormData.adhoc_filters?.length).toEqual(0);
expect(resultFormData.adhoc_filters).toEqual([]);
});
it('Handles the custom time filters and return the correct time shift text', () => {
const resultFormData = getComparisonInfo(
form_data,
ComparisonTimeRangeType.Custom,
{},
);
const expectedFilters = [
{
clause: 'WHERE',
comparator: 'No filter',
expressionType: 'SIMPLE',
operator: 'TEMPORAL_RANGE',
subject: 'order_date',
},
];
expect(resultFormData.adhoc_filters?.length).toEqual(1);
expect(resultFormData.adhoc_filters).toEqual(expectedFilters);
});
});

View File

@ -0,0 +1,32 @@
/*
* 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 {
ComparisonTimeRangeType,
getComparisonFilters,
getComparisonInfo,
} from '@superset-ui/core';
describe('index', () => {
it('exports modules', () => {
[ComparisonTimeRangeType, getComparisonFilters, getComparisonInfo].forEach(
x => expect(x).toBeDefined(),
);
});
});

View File

@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
ComparisonTimeRangeType,
validateTimeComparisonRangeValues,
} from '@superset-ui/core';
import './setup';
describe('validateTimeComparisonRangeValues()', () => {
it('returns the warning message if invalid', () => {
expect(
validateTimeComparisonRangeValues(ComparisonTimeRangeType.Custom, []),
).toBeTruthy();
expect(
validateTimeComparisonRangeValues(
ComparisonTimeRangeType.Custom,
undefined,
),
).toBeTruthy();
expect(
validateTimeComparisonRangeValues(ComparisonTimeRangeType.Custom, null),
).toBeTruthy();
});
it('returns empty array if the input is valid', () => {
expect(
validateTimeComparisonRangeValues(ComparisonTimeRangeType.Year, []),
).toEqual([]);
expect(
validateTimeComparisonRangeValues(
ComparisonTimeRangeType.Year,
undefined,
),
).toEqual([]);
expect(
validateTimeComparisonRangeValues(ComparisonTimeRangeType.Year, null),
).toEqual([]);
expect(
validateTimeComparisonRangeValues(ComparisonTimeRangeType.Custom, [1]),
).toEqual([]);
});
});

View File

@ -62,7 +62,6 @@
"@babel/preset-typescript": "^7.23.3",
"@storybook/react-webpack5": "^7.6.13",
"babel-loader": "^8.1.0",
"chromatic": "^5.4.0",
"fork-ts-checker-webpack-plugin": "^5.0.7",
"ts-loader": "^7.0.4",
"typescript": "^4.5.4"

View File

@ -90,9 +90,6 @@ export default function createQueryStory({
</div>
);
};
story.parameters = {
chromatic: { disable: true },
};
story.args = {
host: 'localhost:8088',
mode: keys[0],

View File

@ -40,7 +40,7 @@ export default {
],
};
export const configureCORS = ({
export const ConfigureCORS = ({
host,
selectEndpoint,
customEndpoint,
@ -84,18 +84,14 @@ export const configureCORS = ({
</div>
);
};
configureCORS.parameters = {
chromatic: { disable: true },
};
configureCORS.args = {
ConfigureCORS.args = {
host: 'localhost:8088',
selectEndpoint: '/api/v1/chart/data',
customEndpoint: '',
methodOption: 'POST', // TODO disable when custonEndpoint and selectEndpoint are empty
postPayloadContents: JSON.stringify({ form_data: bigNumberFormData }),
};
configureCORS.argTypes = {
ConfigureCORS.argTypes = {
host: {
control: 'text',
description: 'Set Superset App host for CORS request',
@ -122,4 +118,4 @@ configureCORS.argTypes = {
description: 'Set POST payload contents',
},
};
configureCORS.storyName = 'Verify CORS';
ConfigureCORS.storyName = 'Verify CORS';

View File

@ -177,15 +177,12 @@ function Heatmap(element, props) {
}
}
function ordScale(k, rangeBands, sortMethod) {
function ordScale(k, rangeBands, sortMethod, formatter) {
let domain = {};
const actualKeys = {}; // hack to preserve type of keys when number
records.forEach(d => {
domain[d[k]] = (domain[d[k]] || 0) + d.v;
actualKeys[d[k]] = d[k];
});
// Not using object.keys() as it converts to strings
const keys = Object.keys(actualKeys).map(s => actualKeys[s]);
const keys = Object.keys(domain).map(k => formatter(k));
if (sortMethod === 'alpha_asc') {
domain = keys.sort(cmp);
} else if (sortMethod === 'alpha_desc') {
@ -252,10 +249,10 @@ function Heatmap(element, props) {
const fp = getNumberFormatter(NumberFormats.PERCENT_2_POINT);
const xScale = ordScale('x', null, sortXAxis);
const yScale = ordScale('y', null, sortYAxis);
const xRbScale = ordScale('x', [0, hmWidth], sortXAxis);
const yRbScale = ordScale('y', [hmHeight, 0], sortYAxis);
const xScale = ordScale('x', null, sortXAxis, xAxisFormatter);
const yScale = ordScale('y', null, sortYAxis, yAxisFormatter);
const xRbScale = ordScale('x', [0, hmWidth], sortXAxis, xAxisFormatter);
const yRbScale = ordScale('y', [hmHeight, 0], sortYAxis, yAxisFormatter);
const X = 0;
const Y = 1;
const heatmapDim = [xRbScale.domain().length, yRbScale.domain().length];

View File

@ -57,11 +57,15 @@ export default function transformProps(chartProps) {
const xAxisFormatter =
coltypes[0] === GenericDataType.Temporal
? getTimeFormatter(timeFormat)
: String;
: coltypes[0] === GenericDataType.Numeric
? Number
: String;
const yAxisFormatter =
coltypes[1] === GenericDataType.Temporal
? getTimeFormatter(timeFormat)
: String;
: coltypes[1] === GenericDataType.Numeric
? Number
: String;
return {
width,
height,

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { createRef, useMemo } from 'react';
import React, { useMemo } from 'react';
import { css, styled, t, useTheme } from '@superset-ui/core';
import { Tooltip } from '@superset-ui/chart-controls';
import {
@ -24,24 +24,33 @@ import {
PopKPIComparisonValueStyleProps,
PopKPIProps,
} from './types';
import { useOverflowDetection } from './useOverflowDetection';
const NumbersContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
overflow: auto;
`;
const ComparisonValue = styled.div<PopKPIComparisonValueStyleProps>`
${({ theme, subheaderFontSize }) => `
font-weight: ${theme.typography.weights.light};
width: 33%;
display: table-cell;
display: flex;
justify-content: center;
font-size: ${subheaderFontSize || 20}px;
text-align: center;
flex: 1 1 0px;
`}
`;
const SymbolWrapper = styled.div<PopKPIComparisonSymbolStyleProps>`
const SymbolWrapper = styled.span<PopKPIComparisonSymbolStyleProps>`
${({ theme, backgroundColor, textColor }) => `
background-color: ${backgroundColor};
color: ${textColor};
padding: ${theme.gridUnit}px ${theme.gridUnit * 2}px;
border-radius: ${theme.gridUnit * 2}px;
display: inline-block;
margin-right: ${theme.gridUnit}px;
`}
`;
@ -61,25 +70,23 @@ export default function PopKPI(props: PopKPIProps) {
comparatorText,
} = props;
const rootElem = createRef<HTMLDivElement>();
const theme = useTheme();
const flexGap = theme.gridUnit * 5;
const wrapperDivStyles = css`
font-family: ${theme.typography.families.sansSerif};
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
padding: ${theme.gridUnit * 4}px;
border-radius: ${theme.gridUnit * 2}px;
align-items: center;
height: ${height}px;
width: ${width}px;
overflow: auto;
`;
const bigValueContainerStyles = css`
font-size: ${headerFontSize || 60}px;
font-weight: ${theme.typography.weights.normal};
text-align: center;
margin-bottom: ${theme.gridUnit * 4}px;
`;
const getArrowIndicatorColor = () => {
@ -135,29 +142,59 @@ export default function PopKPI(props: PopKPIProps) {
tooltipText: t('Percentage difference between the time periods'),
},
],
[prevNumber, valueDifference, percentDifferenceFormattedString],
[
comparatorText,
prevNumber,
valueDifference,
percentDifferenceFormattedString,
],
);
const { isOverflowing, symbolContainerRef, wrapperRef } =
useOverflowDetection(flexGap);
return (
<div ref={rootElem} css={wrapperDivStyles}>
<div css={bigValueContainerStyles}>
{bigNumber}
{percentDifferenceNumber !== 0 && (
<span css={arrowIndicatorStyle}>
{percentDifferenceNumber > 0 ? '↑' : '↓'}
</span>
)}
</div>
<div
css={css`
width: 100%;
display: table;
`}
<div css={wrapperDivStyles} ref={wrapperRef}>
<NumbersContainer
css={
isOverflowing &&
css`
width: fit-content;
margin: auto;
align-items: flex-start;
`
}
>
<div css={bigValueContainerStyles}>
{bigNumber}
{percentDifferenceNumber !== 0 && (
<span css={arrowIndicatorStyle}>
{percentDifferenceNumber > 0 ? '↑' : '↓'}
</span>
)}
</div>
<div
css={css`
display: table-row;
`}
css={[
css`
display: flex;
justify-content: space-around;
gap: ${flexGap}px;
min-width: 0;
flex-shrink: 1;
`,
isOverflowing
? css`
flex-direction: column;
align-items: flex-start;
width: fit-content;
`
: css`
align-items: center;
width: 100%;
`,
]}
ref={symbolContainerRef}
>
{SYMBOLS_WITH_VALUES.map((symbol_with_value, index) => (
<ComparisonValue
@ -182,7 +219,7 @@ export default function PopKPI(props: PopKPIProps) {
</ComparisonValue>
))}
</div>
</div>
</NumbersContainer>
</div>
);
}

View File

@ -0,0 +1,67 @@
/**
* 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 {
buildQueryContext,
getComparisonInfo,
ComparisonTimeRangeType,
QueryFormData,
} from '@superset-ui/core';
export default function buildQuery(formData: QueryFormData) {
const {
cols: groupby,
time_comparison: timeComparison,
extra_form_data: extraFormData,
} = formData;
const queryContextA = buildQueryContext(formData, baseQueryObject => [
{
...baseQueryObject,
groupby,
},
]);
const comparisonFormData = getComparisonInfo(
formData,
timeComparison,
extraFormData,
);
const queryContextB = buildQueryContext(
comparisonFormData,
baseQueryObject => [
{
...baseQueryObject,
groupby,
extras: {
...baseQueryObject.extras,
instant_time_comparison_range:
timeComparison !== ComparisonTimeRangeType.Custom
? timeComparison
: undefined,
},
},
],
);
return {
...queryContextA,
queries: [...queryContextA.queries, ...queryContextB.queries],
};
}

View File

@ -16,26 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
import { ensureIsArray, t, validateNonEmpty } from '@superset-ui/core';
import {
AdhocFilter,
ComparisonTimeRangeType,
SimpleAdhocFilter,
t,
validateTimeComparisonRangeValues,
} from '@superset-ui/core';
import {
ColumnMeta,
ControlPanelConfig,
ControlPanelState,
ControlState,
getStandardizedControls,
sharedControls,
} from '@superset-ui/chart-controls';
const validateTimeComparisonRangeValues = (
timeRangeValue?: any,
controlValue?: any,
) => {
const isCustomTimeRange = timeRangeValue === 'c';
const isCustomControlEmpty = controlValue?.every(
(val: any) => ensureIsArray(val).length === 0,
);
return isCustomTimeRange && isCustomControlEmpty
? [t('Filters for comparison must have a value')]
: [];
};
import { headerFontSize, subheaderFontSize } from '../sharedControls';
const config: ControlPanelConfig = {
controlPanelSections: [
@ -43,17 +39,7 @@ const config: ControlPanelConfig = {
label: t('Query'),
expanded: true,
controlSetRows: [
[
{
name: 'metrics',
config: {
...sharedControls.metrics,
// it's possible to add validators to controls if
// certain selections/types need to be enforced
validators: [validateNonEmpty],
},
},
],
['metric'],
['adhoc_filters'],
[
{
@ -88,20 +74,34 @@ const config: ControlPanelConfig = {
description:
'This only applies when selecting the Range for Comparison Type: Custom',
visibility: ({ controls }) =>
controls?.time_comparison?.value === 'c',
controls?.time_comparison?.value ===
ComparisonTimeRangeType.Custom,
mapStateToProps: (
state: ControlPanelState,
controlState: ControlState,
) => ({
...(sharedControls.adhoc_filters.mapStateToProps?.(
state,
controlState,
) || {}),
externalValidationErrors: validateTimeComparisonRangeValues(
state.controls?.time_comparison?.value,
controlState.value,
),
}),
) => {
const originalMapStateToPropsRes =
sharedControls.adhoc_filters.mapStateToProps?.(
state,
controlState,
) || {};
const columns = originalMapStateToPropsRes.columns.filter(
(col: ColumnMeta) =>
col.is_dttm &&
(state.controls.adhoc_filters.value as AdhocFilter[]).some(
(val: SimpleAdhocFilter) =>
val.subject === col.column_name,
),
);
return {
...originalMapStateToPropsRes,
columns,
externalValidationErrors: validateTimeComparisonRangeValues(
state.controls?.time_comparison?.value,
controlState.value,
),
};
},
},
},
],
@ -121,69 +121,17 @@ const config: ControlPanelConfig = {
['currency_format'],
[
{
name: 'header_font_size',
config: {
type: 'SelectControl',
label: t('Big Number Font Size'),
renderTrigger: true,
clearable: false,
default: 60,
options: [
{
label: t('Tiny'),
value: 16,
},
{
label: t('Small'),
value: 20,
},
{
label: t('Normal'),
value: 30,
},
{
label: t('Large'),
value: 48,
},
{
label: t('Huge'),
value: 60,
},
],
},
...headerFontSize,
config: { ...headerFontSize.config, default: 0.2 },
},
],
[
{
name: 'subheader_font_size',
...subheaderFontSize,
config: {
type: 'SelectControl',
label: t('Subheader Font Size'),
renderTrigger: true,
clearable: false,
default: 40,
options: [
{
label: t('Tiny'),
value: 16,
},
{
label: t('Small'),
value: 20,
},
{
label: t('Normal'),
value: 26,
},
{
label: t('Large'),
value: 32,
},
{
label: t('Huge'),
value: 40,
},
],
...subheaderFontSize.config,
default: 0.125,
label: t('Comparison font size'),
},
},
],
@ -206,7 +154,14 @@ const config: ControlPanelConfig = {
y_axis_format: {
label: t('Number format'),
},
adhoc_filters: {
rerender: ['adhoc_custom'],
},
},
formDataOverrides: formData => ({
...formData,
metric: getStandardizedControls().shiftMetric(),
}),
};
export default config;

View File

@ -20,19 +20,9 @@ import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
import buildQuery from './buildQuery';
import controlPanel from './controlPanel';
import transformProps from './transformProps';
import thumbnail from '../images/thumbnail.png';
import thumbnail from './images/thumbnail.png';
export default class PopKPIPlugin extends ChartPlugin {
/**
* The constructor is used to pass relevant metadata and callbacks that get
* registered in respective registries that are used throughout the library
* and application. A more thorough description of each property is given in
* the respective imported file.
*
* It is worth noting that `buildQuery` and is optional, and only needed for
* advanced visualizations that require either post processing operations
* (pivoting, rolling aggregations, sorting etc) or submitting multiple queries.
*/
constructor() {
const metadata = new ChartMetadata({
category: t('KPI'),
@ -45,6 +35,7 @@ export default class PopKPIPlugin extends ChartPlugin {
t('Percentages'),
t('Report'),
t('Description'),
t('Advanced-Analytics'),
],
thumbnail,
});
@ -52,7 +43,7 @@ export default class PopKPIPlugin extends ChartPlugin {
super({
buildQuery,
controlPanel,
loadChart: () => import('../PopKPI'),
loadChart: () => import('./PopKPI'),
metadata,
transformProps,
});

View File

@ -23,8 +23,9 @@ import {
getValueFormatter,
NumberFormats,
getNumberFormatter,
formatTimeRange,
} from '@superset-ui/core';
import { computeQueryBComparator, formatCustomComparator } from '../utils';
import { getComparisonFontSize, getHeaderFontSize } from './utils';
export const parseMetricValue = (metricValue: number | string | null) => {
if (typeof metricValue === 'string') {
@ -78,23 +79,27 @@ export default function transformProps(chartProps: ChartProps) {
boldText,
headerFontSize,
headerText,
metrics,
metric,
yAxisFormat,
currencyFormat,
subheaderFontSize,
comparisonColorEnabled,
} = formData;
const { data: dataA = [] } = queriesData[0];
const { data: dataB = [] } = queriesData[1];
const {
data: dataB = [],
from_dttm: comparisonFromDatetime,
to_dttm: comparisonToDatetime,
} = queriesData[1];
const data = dataA;
const metricName = getMetricLabel(metrics[0]);
const metricName = getMetricLabel(metric);
let bigNumber: number | string =
data.length === 0 ? 0 : parseMetricValue(data[0][metricName]);
let prevNumber: number | string =
data.length === 0 ? 0 : parseMetricValue(dataB[0][metricName]);
const numberFormatter = getValueFormatter(
metrics[0],
metric,
currencyFormats,
columnFormats,
yAxisFormat,
@ -129,33 +134,23 @@ export default function transformProps(chartProps: ChartProps) {
prevNumber = numberFormatter(prevNumber);
valueDifference = numberFormatter(valueDifference);
const percentDifference: string = formatPercentChange(percentDifferenceNum);
const comparatorText =
formData.timeComparison !== 'c'
? ` ${computeQueryBComparator(
formData.adhocFilters,
formData.timeComparison,
formData.extraFormData,
' - ',
)}`
: `${formatCustomComparator(
formData.adhocCustom,
formData.extraFormData,
)}`;
const comparatorText = formatTimeRange('%Y-%m-%d', [
comparisonFromDatetime,
comparisonToDatetime,
]);
return {
width,
height,
data,
// and now your control data, manipulated as needed, and passed through as props!
metrics,
metricName,
bigNumber,
prevNumber,
valueDifference,
percentDifferenceFormattedString: percentDifference,
boldText,
headerFontSize,
subheaderFontSize,
headerFontSize: getHeaderFontSize(headerFontSize),
subheaderFontSize: getComparisonFontSize(subheaderFontSize),
headerText,
compType,
comparisonColorEnabled,

View File

@ -0,0 +1,63 @@
/**
* 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 { useEffect, useRef, useState } from 'react';
import { debounce } from 'lodash';
export const useOverflowDetection = (flexGap: number) => {
const symbolContainerRef = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
const [isOverflowing, setIsOverflowing] = useState(false);
useEffect(() => {
let obs: ResizeObserver;
const symbolContainerElem = symbolContainerRef.current;
const wrapperElem = wrapperRef.current;
if (symbolContainerElem && wrapperElem) {
const symbolContainerChildrenElems = Array.from(
symbolContainerElem.children,
);
obs = new ResizeObserver(
debounce(() => {
const totalChildrenWidth = symbolContainerChildrenElems.reduce(
(acc, element) =>
// take symbol container's child's scroll width to account for the container growing with display: flex
acc + (element.firstElementChild?.scrollWidth ?? 0),
0,
);
if (
totalChildrenWidth +
flexGap * Math.max(symbolContainerChildrenElems.length - 1, 0) >
wrapperElem.clientWidth
) {
setIsOverflowing(true);
} else {
setIsOverflowing(false);
}
}, 500),
);
obs.observe(document.body);
symbolContainerChildrenElems.forEach(elem => {
obs.observe(elem);
});
}
return () => obs?.disconnect();
}, [flexGap]);
return { isOverflowing, symbolContainerRef, wrapperRef };
};

View File

@ -0,0 +1,39 @@
/**
* 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 { getComparisonFontSize, getHeaderFontSize } from './utils';
test('getHeaderFontSize', () => {
expect(getHeaderFontSize(0.2)).toEqual(16);
expect(getHeaderFontSize(0.3)).toEqual(20);
expect(getHeaderFontSize(0.4)).toEqual(30);
expect(getHeaderFontSize(0.5)).toEqual(48);
expect(getHeaderFontSize(0.6)).toEqual(60);
expect(getHeaderFontSize(0.15)).toEqual(60);
expect(getHeaderFontSize(2)).toEqual(60);
});
test('getComparisonFontSize', () => {
expect(getComparisonFontSize(0.125)).toEqual(16);
expect(getComparisonFontSize(0.15)).toEqual(20);
expect(getComparisonFontSize(0.2)).toEqual(26);
expect(getComparisonFontSize(0.3)).toEqual(32);
expect(getComparisonFontSize(0.4)).toEqual(40);
expect(getComparisonFontSize(0.05)).toEqual(40);
expect(getComparisonFontSize(0.9)).toEqual(40);
});

View File

@ -0,0 +1,59 @@
/**
* 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 { headerFontSize, subheaderFontSize } from '../sharedControls';
const headerFontSizes = [16, 20, 30, 48, 60];
const comparisonFontSizes = [16, 20, 26, 32, 40];
const headerProportionValues =
headerFontSize.config.options.map(
(option: { label: string; value: number }) => option.value,
) ?? [];
const subheaderProportionValues =
subheaderFontSize.config.options.map(
(option: { label: string; value: number }) => option.value,
) ?? [];
const getFontSizeMapping = (
proportionValues: number[],
actualSizes: number[],
) =>
proportionValues.reduce((acc, value, index) => {
acc[value] = actualSizes[index] ?? actualSizes[actualSizes.length - 1];
return acc;
}, {});
const headerFontSizesMapping = getFontSizeMapping(
headerProportionValues,
headerFontSizes,
);
const comparisonFontSizesMapping = getFontSizeMapping(
subheaderProportionValues,
comparisonFontSizes,
);
export const getHeaderFontSize = (proportionValue: number) =>
headerFontSizesMapping[proportionValue] ??
headerFontSizes[headerFontSizes.length - 1];
export const getComparisonFontSize = (proportionValue: number) =>
comparisonFontSizesMapping[proportionValue] ??
comparisonFontSizes[comparisonFontSizes.length - 1];

View File

@ -19,3 +19,4 @@
export { default as BigNumberChartPlugin } from './BigNumberWithTrendline';
export { default as BigNumberTotalChartPlugin } from './BigNumberTotal';
export { default as BigNumberPeriodOverPeriodChartPlugin } from './BigNumberPeriodOverPeriod';

View File

@ -48,11 +48,12 @@ import { getDefaultTooltip } from '../utils/tooltip';
import { Refs } from '../types';
import { getColtypesMapping } from '../utils/series';
const setIntervalBoundsAndColors = (
export const getIntervalBoundsAndColors = (
intervals: string,
intervalColorIndices: string,
colorFn: CategoricalColorScale,
normalizer: number,
min: number,
max: number,
): Array<[number, string]> => {
let intervalBoundsNonNormalized;
let intervalColorIndicesArray;
@ -65,7 +66,7 @@ const setIntervalBoundsAndColors = (
}
const intervalBounds = intervalBoundsNonNormalized.map(
bound => bound / normalizer,
bound => (bound - min) / (max - min),
);
const intervalColors = intervalColorIndicesArray.map(
ind => colorFn.colors[(ind - 1) % colorFn.colors.length],
@ -221,12 +222,12 @@ export default function transformProps(
const axisLabelLength = Math.max(
...axisLabels.map(label => numberFormatter(label).length).concat([1]),
);
const normalizer = max;
const intervalBoundsAndColors = setIntervalBoundsAndColors(
const intervalBoundsAndColors = getIntervalBoundsAndColors(
intervals,
intervalColorIndices,
colorFn,
normalizer,
min,
max,
);
const splitLineDistance =
axisLineWidth + splitLineLength + OFFSETS.ticksFromLine;

View File

@ -410,8 +410,9 @@ export default function transformProps(
rawSeriesB.forEach(entry => {
const entryName = String(entry.name || '');
const seriesName = `${inverted[entryName] || entryName} (1)`;
const colorScaleKey = getOriginalSeries(seriesName, array);
const seriesEntry = inverted[entryName] || entryName;
const seriesName = `${seriesEntry} (1)`;
const colorScaleKey = getOriginalSeries(seriesEntry, array);
const seriesFormatter = getFormatter(
customFormattersSecondary,

View File

@ -575,7 +575,6 @@ export default function transformProps(
right: TIMESERIES_CONSTANTS.toolboxRight,
feature: {
dataZoom: {
yAxisIndex: false,
title: {
zoom: t('zoom area'),
back: t('restore zoom'),
@ -590,6 +589,7 @@ export default function transformProps(
start: TIMESERIES_CONSTANTS.dataZoomStart,
end: TIMESERIES_CONSTANTS.dataZoomEnd,
bottom: TIMESERIES_CONSTANTS.zoomBottom,
yAxisIndex: isHorizontal ? 0 : undefined,
},
]
: [],

View File

@ -570,9 +570,10 @@ export function getPadding(
yAxisTitlePosition && yAxisTitlePosition === 'Top'
? TIMESERIES_CONSTANTS.gridOffsetTop + (Number(yAxisTitleMargin) || 0)
: TIMESERIES_CONSTANTS.gridOffsetTop + yAxisOffset,
bottom: zoomable
? TIMESERIES_CONSTANTS.gridOffsetBottomZoomable + xAxisOffset
: TIMESERIES_CONSTANTS.gridOffsetBottom + xAxisOffset,
bottom:
zoomable && !isHorizontal
? TIMESERIES_CONSTANTS.gridOffsetBottomZoomable + xAxisOffset
: TIMESERIES_CONSTANTS.gridOffsetBottom + xAxisOffset,
left:
yAxisTitlePosition === 'Left'
? TIMESERIES_CONSTANTS.gridOffsetLeft +

View File

@ -32,7 +32,11 @@ export { default as EchartsRadarChartPlugin } from './Radar';
export { default as EchartsFunnelChartPlugin } from './Funnel';
export { default as EchartsTreeChartPlugin } from './Tree';
export { default as EchartsTreemapChartPlugin } from './Treemap';
export { BigNumberChartPlugin, BigNumberTotalChartPlugin } from './BigNumber';
export {
BigNumberChartPlugin,
BigNumberTotalChartPlugin,
BigNumberPeriodOverPeriodChartPlugin,
} from './BigNumber';
export { default as EchartsSunburstChartPlugin } from './Sunburst';
export { default as EchartsBubbleChartPlugin } from './Bubble';
export { default as EchartsWaterfallChartPlugin } from './Waterfall';

View File

@ -16,8 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
import { ChartProps, SqlaFormData, supersetTheme } from '@superset-ui/core';
import transformProps from '../../src/Gauge/transformProps';
import {
CategoricalColorNamespace,
ChartProps,
SqlaFormData,
supersetTheme,
} from '@superset-ui/core';
import transformProps, {
getIntervalBoundsAndColors,
} from '../../src/Gauge/transformProps';
import { EchartsGaugeChartProps } from '../../src/Gauge/types';
describe('Echarts Gauge transformProps', () => {
@ -256,8 +263,9 @@ describe('Echarts Gauge transformProps', () => {
const formData: SqlaFormData = {
...baseFormData,
groupby: ['year', 'platform'],
intervals: '50,100',
intervals: '60,100',
intervalColorIndices: '1,2',
minVal: 20,
};
const queriesData = [
{
@ -342,3 +350,43 @@ describe('Echarts Gauge transformProps', () => {
);
});
});
describe('getIntervalBoundsAndColors', () => {
it('should generate correct interval bounds and colors', () => {
const colorFn = CategoricalColorNamespace.getScale(
'supersetColors' as string,
);
expect(getIntervalBoundsAndColors('', '', colorFn, 0, 10)).toEqual([]);
expect(getIntervalBoundsAndColors('4, 10', '1, 2', colorFn, 0, 10)).toEqual(
[
[0.4, '#1f77b4'],
[1, '#ff7f0e'],
],
);
expect(
getIntervalBoundsAndColors('4, 8, 10', '9, 8, 7', colorFn, 0, 10),
).toEqual([
[0.4, '#bcbd22'],
[0.8, '#7f7f7f'],
[1, '#e377c2'],
]);
expect(getIntervalBoundsAndColors('4, 10', '1, 2', colorFn, 2, 10)).toEqual(
[
[0.25, '#1f77b4'],
[1, '#ff7f0e'],
],
);
expect(
getIntervalBoundsAndColors('-4, 0', '1, 2', colorFn, -10, 0),
).toEqual([
[0.6, '#1f77b4'],
[1, '#ff7f0e'],
]);
expect(
getIntervalBoundsAndColors('-4, -2', '1, 2', colorFn, -10, -2),
).toEqual([
[0.75, '#1f77b4'],
[1, '#ff7f0e'],
]);
});
});

View File

@ -1,87 +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.
\*/
# custom-viz
This plugin provides a BigNumber visualization with period over period time comparisons
### Usage
To build the plugin, run the following commands:
```
npm ci
npm run build
```
Alternatively, to run the plugin in development mode (=rebuilding whenever changes are made), start the dev server with the following command:
```
npm run dev
```
To add the package to Superset, go to the `superset-frontend` subdirectory in your Superset source folder (assuming both the `custom-viz` plugin and `superset` repos are in the same root directory) and run
```
npm i -S ../../plugin-chart-period-over-period-kpi
```
If your Superset plugin exists in the `superset-frontend` directory and you wish to resolve TypeScript errors about `@superset-ui/core` not being resolved correctly, add the following to your `tsconfig.json` file:
```
"references": [
{
"path": "../../packages/superset-ui-chart-controls"
},
{
"path": "../../packages/superset-ui-core"
}
]
```
You may also wish to add the following to the `include` array in `tsconfig.json` to make Superset types available to your plugin:
```
"../../types/**/*"
```
Finally, if you wish to ensure your plugin `tsconfig.json` is aligned with the root Superset project, you may add the following to your `tsconfig.json` file:
```
"extends": "../../tsconfig.json",
```
After this edit the `superset-frontend/src/visualizations/presets/MainPreset.js` and make the following changes:
```js
import { PopKPIPlugin } from '@superset-ui/plugin-chart-period-over-period-kpi';
```
to import the plugin and later add the following to the array that's passed to the `plugins` property:
```js
new PopKPIPlugin().configure({ key: 'pop_kpi' }),
```
After that the plugin should show up when you run Superset, e.g. the development server:
```
npm run dev-server
```

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