Compare commits

..
Author SHA1 Message Date
Sam Firke 120a92728e add two new options for boxplot percentiles 2025-01-31 15:34:59 -05:00
558 changed files with 13366 additions and 13722 deletions
+1 -1
View File
@@ -16,7 +16,7 @@
# Notify E2E test maintainers of changes
/superset-frontend/cypress-base/ @sadpandajoe @geido @eschutho @rusackas @betodealmeida @mistercrunch
/superset-frontend/cypress-base/ @sadpandajoe @geido @eschutho @rusackas @betodealmeida
# Notify PMC members of changes to GitHub Actions
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: './superset-embedded-sdk/.nvmrc'
node-version: "20"
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run ci:release
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: './superset-embedded-sdk/.nvmrc'
node-version: "20"
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm test
+16 -51
View File
@@ -50,45 +50,17 @@ jobs:
echo "result=up" >> $GITHUB_OUTPUT
else
echo "result=noop" >> $GITHUB_OUTPUT
exit 1
fi
- name: Get event SHA
id: get-sha
if: steps.eval-label.outputs.result == 'up'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
let prSha;
// If event is workflow_dispatch, use the issue_number from inputs
if (context.eventName === "workflow_dispatch") {
const prNumber = "${{ github.event.inputs.issue_number }}";
if (!prNumber) {
console.log("No PR number found.");
return;
}
// Fetch PR details using the provided issue_number
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
prSha = pr.head.sha;
} else {
// If it's not workflow_dispatch, use the PR head sha from the event
prSha = context.payload.pull_request.head.sha;
}
console.log(`PR SHA: ${prSha}`);
core.setOutput("sha", prSha);
run: |
echo "sha=${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT
- name: Looking for feature flags in PR description
uses: actions/github-script@v7
id: eval-feature-flags
if: steps.eval-label.outputs.result == 'up'
with:
script: |
const description = context.payload.pull_request
@@ -109,7 +81,6 @@ jobs:
- name: Reply with confirmation comment
uses: actions/github-script@v7
if: steps.eval-label.outputs.result == 'up'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
@@ -190,9 +161,8 @@ jobs:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: superset-ci
IMAGE_TAG: apache/superset:${{ needs.ephemeral-env-label.outputs.sha }}-ci
PR_NUMBER: ${{ github.event.inputs.issue_number || github.event.pull_request.number }}
run: |
docker tag $IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:pr-$PR_NUMBER-ci
docker tag $IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci
docker push -a $ECR_REGISTRY/$ECR_REPOSITORY
ephemeral-env-up:
@@ -223,13 +193,11 @@ jobs:
- name: Check target image exists in ECR
id: check-image
continue-on-error: true
env:
PR_NUMBER: ${{ github.event.inputs.issue_number || github.event.pull_request.number }}
run: |
aws ecr describe-images \
--registry-id $(echo "${{ steps.login-ecr.outputs.registry }}" | grep -Eo "^[0-9]+") \
--repository-name superset-ci \
--image-ids imageTag=pr-$PR_NUMBER-ci
--image-ids imageTag=pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci
- name: Fail on missing container image
if: steps.check-image.outcome == 'failure'
@@ -239,7 +207,7 @@ jobs:
script: |
const errMsg = '@${{ github.event.comment.user.login }} Container image not yet published for this PR. Please try again when build is complete.';
github.rest.issues.createComment({
issue_number: ${{ github.event.inputs.issue_number || github.event.pull_request.number }},
issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }},
owner: context.repo.owner,
repo: context.repo.repo,
body: errMsg
@@ -252,7 +220,7 @@ jobs:
with:
task-definition: .github/workflows/ecs-task-definition.json
container-name: superset-ci
image: ${{ steps.login-ecr.outputs.registry }}/superset-ci:pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-ci
image: ${{ steps.login-ecr.outputs.registry }}/superset-ci:pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci
- name: Update env vars in the Amazon ECS task definition
run: |
@@ -261,30 +229,29 @@ jobs:
- name: Describe ECS service
id: describe-services
run: |
echo "active=$(aws ecs describe-services --cluster superset-ci --services pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service | jq '.services[] | select(.status == "ACTIVE") | any')" >> $GITHUB_OUTPUT
echo "active=$(aws ecs describe-services --cluster superset-ci --services pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service | jq '.services[] | select(.status == "ACTIVE") | any')" >> $GITHUB_OUTPUT
- name: Create ECS service
id: create-service
if: steps.describe-services.outputs.active != 'true'
env:
ECR_SUBNETS: subnet-0e15a5034b4121710,subnet-0e8efef4a72224974
ECR_SECURITY_GROUP: sg-092ff3a6ae0574d91
PR_NUMBER: ${{ github.event.inputs.issue_number || github.event.pull_request.number }}
run: |
aws ecs create-service \
--cluster superset-ci \
--service-name pr-$PR_NUMBER-service \
--service-name pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service \
--task-definition superset-ci \
--launch-type FARGATE \
--desired-count 1 \
--platform-version LATEST \
--network-configuration "awsvpcConfiguration={subnets=[$ECR_SUBNETS],securityGroups=[$ECR_SECURITY_GROUP],assignPublicIp=ENABLED}" \
--tags key=pr,value=$PR_NUMBER key=github_user,value=${{ github.actor }}
--tags key=pr,value=${{ github.event.inputs.issue_number || github.event.issue.number }} key=github_user,value=${{ github.actor }}
- name: Deploy Amazon ECS task definition
id: deploy-task
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service
service: pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service
cluster: superset-ci
wait-for-service-stability: true
wait-for-minutes: 10
@@ -292,7 +259,7 @@ jobs:
- name: List tasks
id: list-tasks
run: |
echo "task=$(aws ecs list-tasks --cluster superset-ci --service-name pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service | jq '.taskArns | first')" >> $GITHUB_OUTPUT
echo "task=$(aws ecs list-tasks --cluster superset-ci --service-name pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service | jq '.taskArns | first')" >> $GITHUB_OUTPUT
- name: Get network interface
id: get-eni
run: |
@@ -307,22 +274,20 @@ jobs:
with:
github-token: ${{github.token}}
script: |
const issue_number = context.payload.inputs?.issue_number || context.issue.number;
github.rest.issues.createComment({
issue_number: issue_number,
issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }},
owner: context.repo.owner,
repo: context.repo.repo,
body: `@${{ github.actor }} Ephemeral environment spinning up at http://${{ steps.get-ip.outputs.ip }}:8080. Credentials are 'admin'/'admin'. Please allow several minutes for bootstrapping and startup.`
});
body: '@${{ github.actor }} Ephemeral environment spinning up at http://${{ steps.get-ip.outputs.ip }}:8080. Credentials are `admin`/`admin`. Please allow several minutes for bootstrapping and startup.'
})
- name: Comment (failure)
if: ${{ failure() }}
uses: actions/github-script@v7
with:
github-token: ${{github.token}}
script: |
const issue_number = context.payload.inputs?.issue_number || context.issue.number;
github.rest.issues.createComment({
issue_number: issue_number,
issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }},
owner: context.repo.owner,
repo: context.repo.repo,
body: '@${{ github.event.inputs.user_login || github.event.comment.user.login }} Ephemeral environment creation failed. Please check the Actions logs for details.'
+2 -17
View File
@@ -38,26 +38,11 @@ jobs:
echo "HOMEBREW_CELLAR=$HOMEBREW_CELLAR" >>"${GITHUB_ENV}"
echo "HOMEBREW_REPOSITORY=$HOMEBREW_REPOSITORY" >>"${GITHUB_ENV}"
brew install norwoodj/tap/helm-docs
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Frontend Dependencies
run: |
cd superset-frontend
npm install
- name: Install Docs Dependencies
run: |
cd docs
yarn install
- name: pre-commit
run: |
set +e # Don't exit immediately on failure
# Skip auto-fixing in CI to ensure changes are committed locally
export SKIP_FIX=1
# Skip eslint as it requires `npm ci` and is executed in another job
export SKIP=eslint
pre-commit run --all-files
if [ $? -ne 0 ] || ! git diff --quiet --exit-code; then
echo "❌ Pre-commit check failed."
+8 -2
View File
@@ -24,7 +24,13 @@ jobs:
needs: config
if: needs.config.outputs.has-secrets
name: Bump version and publish package(s)
runs-on: ubuntu-24.04
strategy:
matrix:
node-version: [20]
steps:
- uses: actions/checkout@v4
with:
@@ -40,11 +46,11 @@ jobs:
git fetch --prune --unshallow
git tag -d `git tag | grep -E '^trigger-'`
- name: Install Node.js
- name: Use Node.js ${{ matrix.node-version }}
if: env.HAS_TAGS
uses: actions/setup-node@v4
with:
node-version-file: './superset-frontend/.nvmrc'
node-version: ${{ matrix.node-version }}
- name: Cache npm
if: env.HAS_TAGS
@@ -26,6 +26,7 @@ jobs:
fail-fast: false
matrix:
browser: ["chrome"]
node: [20]
env:
SUPERSET_ENV: development
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
@@ -65,7 +66,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: './superset-frontend/.nvmrc'
node-version: ${{ matrix.node }}
- name: Install npm dependencies
uses: ./.github/actions/cached-dependencies
with:
@@ -28,6 +28,9 @@ jobs:
needs: config
if: needs.config.outputs.has-secrets
runs-on: ubuntu-24.04
strategy:
matrix:
node: [20]
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
@@ -38,7 +41,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: './superset-frontend/.nvmrc'
node-version: ${{ matrix.node }}
- name: Install eyes-storybook dependencies
uses: ./.github/actions/cached-dependencies
with:
+2 -2
View File
@@ -35,10 +35,10 @@ jobs:
with:
persist-credentials: false
submodules: recursive
- name: Set up Node.js
- name: Set up Node.js 20
uses: actions/setup-node@v4
with:
node-version-file: './docs/.nvmrc'
node-version: '20'
- name: Setup Python
uses: ./.github/actions/setup-backend/
- uses: actions/setup-java@v4
+2 -2
View File
@@ -60,10 +60,10 @@ jobs:
with:
persist-credentials: false
submodules: recursive
- name: Set up Node.js
- name: Set up Node.js 20
uses: actions/setup-node@v4
with:
node-version-file: './docs/.nvmrc'
node-version: '20'
- name: yarn install
run: |
yarn install --check-cache
+1 -1
View File
@@ -109,7 +109,7 @@ jobs:
if: steps.check.outputs.python || steps.check.outputs.frontend
uses: actions/setup-node@v4
with:
node-version-file: './superset-frontend/.nvmrc'
node-version: "20"
- name: Install npm dependencies
if: steps.check.outputs.python || steps.check.outputs.frontend
uses: ./.github/actions/cached-dependencies
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
if: steps.check.outputs.frontend
uses: actions/setup-node@v4
with:
node-version-file: './superset-frontend/.nvmrc'
node-version: '18'
- name: Install dependencies
if: steps.check.outputs.frontend
uses: ./.github/actions/cached-dependencies
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: './superset-frontend/.nvmrc'
node-version: '20'
- name: Install Dependencies
run: npm install
-1
View File
@@ -21,7 +21,6 @@
*.swp
__pycache__
.aider*
.local
.cache
.bento*
+15 -35
View File
@@ -20,7 +20,7 @@ repos:
hooks:
- id: auto-walrus
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.15.0
rev: v1.13.0
hooks:
- id: mypy
args: [--check-untyped-defs]
@@ -52,40 +52,22 @@ repos:
- id: trailing-whitespace
exclude: ^.*\.(snap)
args: ["--markdown-linebreak-ext=md"]
- repo: local
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8 # Use the sha or tag you want to point at
hooks:
- id: prettier
name: prettier
entry: bash -c 'cd superset-frontend && if [ -z "$SKIP_FIX" ]; then npm run format -- --log-level error; else npm run prettier-check; fi'
language: system
files: '^superset-frontend/(src|spec|cypress-base|plugins|packages|.storybook)/.+\.(js|jsx|ts|tsx|css|less|scss|sass)$|^superset-frontend/package\.json$'
pass_filenames: true
- id: eslint-frontend
name: eslint (frontend)
entry: bash -c 'cd superset-frontend && if [ -z "$SKIP_FIX" ]; then npm run lint-fix; else npm run eslint; fi'
additional_dependencies:
- prettier@3.3.3
args: ["--ignore-path=./superset-frontend/.prettierignore"]
files: "superset-frontend"
- repo: local
hooks:
- id: eslint
name: eslint
entry: bash -c 'cd superset-frontend && npm run eslint -- $(echo "$@" | sed "s|superset-frontend/||g")'
language: system
pass_filenames: true
files: ^superset-frontend/.*\.(js|jsx|ts|tsx)$
# TODO - re-assess using this script-based linting rather than the npm command. Both approaches have their merits
# - id: eslint
# name: eslint
# entry: ./scripts/eslint.sh
# language: script
# pass_filenames: true
# files: ^superset-frontend\/.*\.(js|jsx|ts|tsx)$
- id: eslint-docs
name: eslint (docs)
entry: bash -c 'cd docs && FILES=$(echo "$@" | sed "s|docs/||g") && yarn eslint --ext .js,.jsx,.ts,.tsx --quiet $([ -z "$SKIP_FIX" ] && echo "--fix") $FILES'
language: system
pass_filenames: true
files: ^docs/.*\.(js|jsx|ts|tsx)$
- id: type-checking-frontend
name: Type-Checking (Frontend)
entry: bash -c './scripts/check-type.js package=superset-frontend excludeDeclarationDir=cypress-base'
language: system
files: ^superset-frontend\/.*\.(js|jsx|ts|tsx)$
exclude: ^superset-frontend/cypress-base\/
require_serial: true
files: \.(js|jsx|ts|tsx)$
# blacklist unsafe functions like make_url (see #19526)
- repo: https://github.com/skorokithakis/blacklist-pre-commit-hook
rev: e2f070289d8eddcaec0b580d3bde29437e7c8221
@@ -97,11 +79,9 @@ repos:
hooks:
- id: helm-docs
files: helm
verbose: false
args: ["--log-level", "error"]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.7
rev: v0.8.0
hooks:
- id: ruff
args: [--fix]
args: [ --fix ]
- id: ruff-format
+3 -9
View File
@@ -18,19 +18,16 @@
######################################################################
# Node stage to deal with static asset construction
######################################################################
ARG PY_VER=3.11.11-slim-bookworm
ARG PY_VER=3.11-slim-bookworm
# If BUILDPLATFORM is null, set it to 'amd64' (or leave as is otherwise).
ARG BUILDPLATFORM=${BUILDPLATFORM:-amd64}
# Include translations in the final build
ARG BUILD_TRANSLATIONS="false"
######################################################################
# superset-node-ci used as a base for building frontend assets and CI
######################################################################
FROM --platform=${BUILDPLATFORM} node:20-bullseye-slim AS superset-node-ci
ARG BUILD_TRANSLATIONS
ARG BUILD_TRANSLATIONS="false" # Include translations in the final build
ENV BUILD_TRANSLATIONS=${BUILD_TRANSLATIONS}
ARG DEV_MODE="false" # Skip frontend build in dev mode
ENV DEV_MODE=${DEV_MODE}
@@ -125,13 +122,10 @@ ENV PATH="/app/.venv/bin:${PATH}"
######################################################################
FROM python-base AS python-translation-compiler
ARG BUILD_TRANSLATIONS
ENV BUILD_TRANSLATIONS=${BUILD_TRANSLATIONS}
# Install Python dependencies using docker/pip-install.sh
COPY requirements/translations.txt requirements/
RUN --mount=type=cache,target=/root/.cache/uv \
. /app/.venv/bin/activate && /app/docker/pip-install.sh --requires-build-essential -r requirements/translations.txt
/app/docker/pip-install.sh --requires-build-essential -r requirements/translations.txt
COPY superset/translations/ /app/translations_mo/
RUN if [ "$BUILD_TRANSLATIONS" = "true" ]; then \
+1 -3
View File
@@ -73,8 +73,7 @@ Superset provides:
**Video Overview**
<!-- File hosted here https://github.com/apache/superset-site/raw/lfs/superset-video-4k.mp4 -->
[superset-video-1080p.webm](https://github.com/user-attachments/assets/b37388f7-a971-409c-96a7-90c4e31322e6)
[superset-video-4k.webm](https://github.com/apache/superset/assets/812905/da036bc2-150c-4ee7-80f9-75e63210ff76)
<br/>
@@ -138,7 +137,6 @@ Here are some of the major database solutions that are supported:
<img src="https://superset.apache.org/img/databases/sap-hana.png" alt="oceanbase" border="0" width="220" />
<img src="https://superset.apache.org/img/databases/denodo.png" alt="denodo" border="0" width="200" />
<img src="https://superset.apache.org/img/databases/ydb.svg" alt="ydb" border="0" width="200" />
<img src="https://superset.apache.org/img/databases/tdengine.png" alt="TDengine" border="0" width="200" />
</p>
**A more comprehensive list of supported databases** along with the configuration instructions can be found [here](https://superset.apache.org/docs/configuration/databases).
+3 -3
View File
@@ -30,12 +30,12 @@ RUN apt-get install -y apt-transport-https apt-utils
# Install superset dependencies
# https://superset.apache.org/docs/installation/installing-superset-from-scratch
RUN apt-get install -y build-essential libssl-dev \
libffi-dev python3-dev libsasl2-dev libldap2-dev libxi-dev chromium zstd
libffi-dev python3-dev libsasl2-dev libldap2-dev libxi-dev chromium
# Install nodejs for custom build
# https://nodejs.org/en/download/package-manager/
RUN set -eux; \
curl -sL https://deb.nodesource.com/setup_20.x | bash -; \
curl -sL https://deb.nodesource.com/setup_18.x | bash -; \
apt-get install -y nodejs; \
node --version;
RUN if ! which npm; then apt-get install -y npm; fi
@@ -64,7 +64,7 @@ RUN pip install --upgrade setuptools pip \
RUN flask fab babel-compile --target superset/translations
ENV PATH=/home/superset/superset/bin:$PATH \
PYTHONPATH=/home/superset/superset/ \
PYTHONPATH=/home/superset/superset/:$PYTHONPATH \
SUPERSET_TESTENV=true
COPY from_tarball_entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
+9 -10
View File
@@ -29,16 +29,13 @@ RUN apt-get install -y apt-transport-https apt-utils
# Install superset dependencies
# https://superset.apache.org/docs/installation/installing-superset-from-scratch
RUN apt-get install -y subversion build-essential libssl-dev \
libffi-dev python3-dev libsasl2-dev libldap2-dev libxi-dev chromium zstd
RUN apt-get install -y build-essential libssl-dev \
libffi-dev python3-dev libsasl2-dev libldap2-dev libxi-dev chromium
# Install nodejs for custom build
# https://nodejs.org/en/download/package-manager/
RUN set -eux; \
curl -sL https://deb.nodesource.com/setup_20.x | bash -; \
apt-get install -y nodejs; \
node --version;
RUN if ! which npm; then apt-get install -y npm; fi
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - \
&& apt-get install -y nodejs
RUN mkdir -p /home/superset
RUN chown superset /home/superset
@@ -49,12 +46,14 @@ ARG VERSION
# Can fetch source from svn or copy tarball from local mounted directory
RUN svn co https://dist.apache.org/repos/dist/dev/superset/$VERSION ./
RUN tar -xvf *.tar.gz
WORKDIR /home/superset/apache-superset-$VERSION/superset-frontend
WORKDIR apache-superset-$VERSION
RUN npm ci \
RUN cd superset-frontend \
&& npm ci \
&& npm run build \
&& rm -rf node_modules
WORKDIR /home/superset/apache-superset-$VERSION
RUN pip install --upgrade setuptools pip \
&& pip install -r requirements/base.txt \
@@ -63,6 +62,6 @@ RUN pip install --upgrade setuptools pip \
RUN flask fab babel-compile --target superset/translations
ENV PATH=/home/superset/superset/bin:$PATH \
PYTHONPATH=/home/superset/superset/
PYTHONPATH=/home/superset/superset/:$PYTHONPATH
COPY from_tarball_entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
+6 -5
View File
@@ -232,7 +232,8 @@ class GitChangeLog:
for log in self._logs:
yield {
"pr_number": log.pr_number,
"pr_link": f"https://github.com/{SUPERSET_REPO}/pull/{log.pr_number}",
"pr_link": f"https://github.com/{SUPERSET_REPO}/pull/"
f"{log.pr_number}",
"message": log.message,
"time": log.time,
"author": log.author,
@@ -322,9 +323,9 @@ class BaseParameters:
def print_title(message: str) -> None:
print(f"{50 * '-'}")
print(f"{50*'-'}")
print(message)
print(f"{50 * '-'}")
print(f"{50*'-'}")
@click.group()
@@ -348,14 +349,14 @@ def compare(base_parameters: BaseParameters) -> None:
previous_logs = base_parameters.previous_logs
current_logs = base_parameters.current_logs
print_title(
f"Pull requests from {current_logs.git_ref} not in {previous_logs.git_ref}"
f"Pull requests from " f"{current_logs.git_ref} not in {previous_logs.git_ref}"
)
previous_diff_logs = previous_logs.diff(current_logs)
for diff_log in previous_diff_logs:
print(f"{diff_log}")
print_title(
f"Pull requests from {previous_logs.git_ref} not in {current_logs.git_ref}"
f"Pull requests from " f"{previous_logs.git_ref} not in {current_logs.git_ref}"
)
current_diff_logs = current_logs.diff(previous_logs)
for diff_log in current_diff_logs:
-1
View File
@@ -116,7 +116,6 @@ Join our growing community!
- [PubNub](https://pubnub.com) [@jzucker2]
- [ReadyTech](https://www.readytech.io)
- [Reward Gateway](https://www.rewardgateway.com)
- [RIADVICE](https://riadvice.tn) [@riadvice]
- [ScopeAI](https://www.getscopeai.com) [@iloveluce]
- [Showmax](https://showmax.com) [@bobek]
- [TechAudit](https://www.techaudit.info) [@ETselikov]
+14 -2
View File
@@ -43,8 +43,8 @@ under the License.
| can this form post on ResetPasswordView |:heavy_check_mark:|O|O|O|
| can this form get on ResetMyPasswordView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can this form post on ResetMyPasswordView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can this form get on UserInfoEditView |:heavy_check_mark:|O|O|O|
| can this form post on UserInfoEditView |:heavy_check_mark:|O|O|O|
| can this form get on UserInfoEditView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can this form post on UserInfoEditView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can show on UserDBModelView |:heavy_check_mark:|O|O|O|
| can edit on UserDBModelView |:heavy_check_mark:|O|O|O|
| can delete on UserDBModelView |:heavy_check_mark:|O|O|O|
@@ -65,6 +65,7 @@ under the License.
| can get on MenuApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can list on AsyncEventsRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can invalidate on CacheRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can function names on Database |:heavy_check_mark:|O|O|O|
| can csv upload on Database |:heavy_check_mark:|O|O|O|
| can excel upload on Database |:heavy_check_mark:|O|O|O|
| can query form data on Api |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
@@ -75,6 +76,7 @@ under the License.
| can get on Datasource |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can my queries on SqlLab |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
| can log on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can schemas access for csv upload on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can import dashboards on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can schemas on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can sqllab history on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
@@ -116,6 +118,8 @@ under the License.
| menu access on Data |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Databases |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Datasets |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Upload a CSV |:heavy_check_mark:|:heavy_check_mark:|O|O|
| menu access on Upload Excel |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Charts |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Dashboards |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on SQL Lab |:heavy_check_mark:|O|O|:heavy_check_mark:|
@@ -125,6 +129,13 @@ under the License.
| all datasource access on all_datasource_access |:heavy_check_mark:|:heavy_check_mark:|O|O|
| all database access on all_database_access |:heavy_check_mark:|:heavy_check_mark:|O|O|
| all query access on all_query_access |:heavy_check_mark:|O|O|O|
| can edit on UserOAuthModelView |:heavy_check_mark:|O|O|O|
| can list on UserOAuthModelView |:heavy_check_mark:|O|O|O|
| can show on UserOAuthModelView |:heavy_check_mark:|O|O|O|
| can userinfo on UserOAuthModelView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can add on UserOAuthModelView |:heavy_check_mark:|O|O|O|
| can delete on UserOAuthModelView |:heavy_check_mark:|O|O|O|
| userinfoedit on UserOAuthModelView |:heavy_check_mark:|O|O|O|
| can write on DynamicPlugin |:heavy_check_mark:|O|O|O|
| can edit on DynamicPlugin |:heavy_check_mark:|O|O|O|
| can list on DynamicPlugin |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
@@ -181,6 +192,7 @@ under the License.
| can share chart on Superset |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can this form get on ColumnarToDatabaseView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can this form post on ColumnarToDatabaseView |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| menu access on Upload a Columnar file |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can export on Chart |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can write on DashboardFilterStateRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
| can read on DashboardFilterStateRestApi |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
+2 -3
View File
@@ -24,9 +24,8 @@ assists people when migrating to a new version.
## Next
- [31976](https://github.com/apache/superset/pull/31976) Removed the `DISABLE_LEGACY_DATASOURCE_EDITOR` feature flag. The previous value of the feature flag was `True` and now the feature is permanently removed.
- [31959](https://github.com/apache/superset/pull/32000) Removes CSV_UPLOAD_MAX_SIZE config, use your web server to control file upload size.
- [31959](https://github.com/apache/superset/pull/31959) Removes the following endpoints from data uploads: `/api/v1/database/<id>/<file type>_upload` and `/api/v1/database/<file type>_metadata`, in favour of new one (Details on the PR). And simplifies permissions.
- [31959](https://github.com/apache/superset/pull/31959) Removes the following endpoints from data uploads: /api/v1/database/<id>/<file type>_upload and /api/v1/database/<file type>_metadata, in favour of new one (Details on the PR). And simplifies permissions.
- [31844](https://github.com/apache/superset/pull/31844) The `ALERT_REPORTS_EXECUTE_AS` and `THUMBNAILS_EXECUTE_AS` config parameters have been renamed to `ALERT_REPORTS_EXECUTORS` and `THUMBNAILS_EXECUTORS` respectively. A new config flag `CACHE_WARMUP_EXECUTORS` has also been introduced to be able to control which user is used to execute cache warmup tasks. Finally, the config flag `THUMBNAILS_SELENIUM_USER` has been removed. To use a fixed executor for async tasks, use the new `FixedExecutor` class. See the config and docs for more info on setting up different executor profiles.
- [31894](https://github.com/apache/superset/pull/31894) Domain sharding is deprecated in favor of HTTP2. The `SUPERSET_WEBSERVER_DOMAINS` configuration will be removed in the next major version (6.0)
- [31794](https://github.com/apache/superset/pull/31794) Removed the previously deprecated `DASHBOARD_CROSS_FILTERS` feature flag
@@ -46,7 +45,7 @@ assists people when migrating to a new version.
- [25166](https://github.com/apache/superset/pull/25166) Changed the default configuration of `UPLOAD_FOLDER` from `/app/static/uploads/` to `/static/uploads/`. It also removed the unused `IMG_UPLOAD_FOLDER` and `IMG_UPLOAD_URL` configuration options.
- [30284](https://github.com/apache/superset/pull/30284) Deprecated GLOBAL_ASYNC_QUERIES_REDIS_CONFIG in favor of the new GLOBAL_ASYNC_QUERIES_CACHE_BACKEND configuration. To leverage Redis Sentinel, set CACHE_TYPE to RedisSentinelCache, or use RedisCache for standalone Redis
- [31961](https://github.com/apache/superset/pull/31961) Upgraded React from version 16.13.1 to 17.0.2. If you are using custom frontend extensions or plugins, you may need to update them to be compatible with React 17.
- [31260](https://github.com/apache/superset/pull/31260) Docker images now use `uv pip install` instead of `pip install` to manage the python envrionment. Most docker-based deployments will be affected, whether you derive one of the published images, or have custom bootstrap script that install python libraries (drivers)
### Potential Downtime
-6
View File
@@ -112,12 +112,6 @@ http {
proxy_set_header Host $host;
}
location /static {
proxy_pass http://host.docker.internal:9000; # Proxy to superset-node
proxy_http_version 1.1;
proxy_set_header Host $host;
}
location / {
proxy_pass http://superset_app;
proxy_set_header Host $host;
+1 -1
View File
@@ -129,7 +129,7 @@ try:
from superset_config_docker import * # noqa
logger.info(
f"Loaded your Docker configuration at [{superset_config_docker.__file__}]"
f"Loaded your Docker configuration at " f"[{superset_config_docker.__file__}]"
)
except ImportError:
logger.info("Using default Docker config...")
+1 -1
View File
@@ -18,6 +18,6 @@ under the License.
-->
This is the public documentation site for Superset, built using
[Docusaurus 3](https://docusaurus.io/). See
[Docusaurus 2](https://docusaurus.io/). See
[CONTRIBUTING.md](../CONTRIBUTING.md#documentation) for documentation on
contributing to documentation.
-1
View File
@@ -1,4 +1,3 @@
/* eslint-env node */
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
+1
View File
@@ -4,6 +4,7 @@ hide_title: true
sidebar_position: 10
---
import { Buffer } from 'buffer/index.js';
import SwaggerUI from 'swagger-ui-react';
import openapi from '/resources/openapi.json';
import 'swagger-ui-react/swagger-ui.css';
-37
View File
@@ -69,7 +69,6 @@ are compatible with Superset.
| [MySQL](/docs/configuration/databases#mysql) | `pip install mysqlclient` | `mysql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
| [OceanBase](/docs/configuration/databases#oceanbase) | `pip install oceanbase_py` | `oceanbase://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
| [Oracle](/docs/configuration/databases#oracle) | `pip install cx_Oracle` | `oracle://` |
| [Parseable](/docs/configuration/databases#parseable) | `pip install sqlalchemy-parseable` | `parseable://<UserName>:<DBPassword>@<Database Host>/<Stream Name>` |
| [PostgreSQL](/docs/configuration/databases#postgres) | `pip install psycopg2` | `postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
| [Presto](/docs/configuration/databases#presto) | `pip install pyhive` | `presto://` |
| [Rockset](/docs/configuration/databases#rockset) | `pip install rockset-sqlalchemy` | `rockset://<api_key>:@<api_server>` |
@@ -78,7 +77,6 @@ are compatible with Superset.
| [Snowflake](/docs/configuration/databases#snowflake) | `pip install snowflake-sqlalchemy` | `snowflake://{user}:{password}@{account}.{region}/{database}?role={role}&warehouse={warehouse}` |
| SQLite | No additional library needed | `sqlite://path/to/file.db?check_same_thread=false` |
| [SQL Server](/docs/configuration/databases#sql-server) | `pip install pymssql` | `mssql+pymssql://` |
| [TDengine](/docs/configuration/databases#tdengine) | `pip install taospy` `pip install taos-ws-py` | `taosws://<user>:<password>@<host>:<port>` |
| [Teradata](/docs/configuration/databases#teradata) | `pip install teradatasqlalchemy` | `teradatasql://{user}:{password}@{host}` |
| [TimescaleDB](/docs/configuration/databases#timescaledb) | `pip install psycopg2` | `postgresql://<UserName>:<DBPassword>@<Database Host>:<Port>/<Database Name>` |
| [Trino](/docs/configuration/databases#trino) | `pip install trino` | `trino://{username}:{password}@{hostname}:{port}/{catalog}` |
@@ -1076,23 +1074,6 @@ The connection string is formatted as follows:
oracle://<username>:<password>@<hostname>:<port>
```
#### Parseable
[Parseable](https://www.parseable.io) is a distributed log analytics database that provides SQL-like query interface for log data. The recommended connector library is [sqlalchemy-parseable](https://github.com/parseablehq/sqlalchemy-parseable).
The connection string is formatted as follows:
```
parseable://<username>:<password>@<hostname>:<port>/<stream_name>
```
For example:
```
parseable://admin:admin@demo.parseable.com:443/ingress-nginx
```
Note: The stream_name in the URI represents the Parseable logstream you want to query. You can use both HTTP (port 80) and HTTPS (port 443) connections.
#### Apache Pinot
@@ -1355,24 +1336,6 @@ starrocks://<User>:<Password>@<Host>:<Port>/<Catalog>.<Database>
StarRocks maintains their Superset docuementation [here](https://docs.starrocks.io/docs/integrations/BI_integrations/Superset/).
:::
#### TDengine
[TDengine](https://www.tdengine.com) is a High-Performance, Scalable Time-Series Database for Industrial IoT and provides SQL-like query interface.
The recommended connector library for TDengine is [taospy](https://pypi.org/project/taospy/) and [taos-ws-py](https://pypi.org/project/taos-ws-py/)
The expected connection string is formatted as follows:
```
taosws://<user>:<password>@<host>:<port>
```
For example:
```
taosws://root:taosdata@127.0.0.1:6041
```
#### Teradata
The recommended connector library is
@@ -0,0 +1,6 @@
---
title: Setup SSH Tunneling
hide_title: true
sidebar_position: 8
version: 1
---
+4 -4
View File
@@ -10,7 +10,7 @@ version: 1
The latest documentation and tutorial are available at https://superset.apache.org/.
The documentation site is built using [Docusaurus 3](https://docusaurus.io/), a modern
The documentation site is built using [Docusaurus 2](https://docusaurus.io/), a modern
static website generator, the source for which resides in `./docs`.
### Local Development
@@ -223,9 +223,9 @@ To run a single test file:
npm run test -- path/to/file.js
```
### E2E Integration Testing
### e2e Integration Testing
For E2E testing, we recommend that you use a `docker compose` backend
For e2e testing, we recommend that you use a `docker compose` backend
```bash
CYPRESS_CONFIG=true docker compose up --build
@@ -411,7 +411,7 @@ See [set capabilities for a container](https://kubernetes.io/docs/tasks/configur
Once the pod is running as root and has the `SYS_PTRACE` capability it will be able to debug the Flask app.
You can follow the same instructions as in `docker compose`. Enter the pod and install the required library and packages: gdb, netstat and debugpy.
You can follow the same instructions as in `docker compose`. Enter the pod and install the required library and packages; gdb, netstat and debugpy.
Often in a Kubernetes environment nodes are not addressable from outside the cluster. VSCode will thus be unable to remotely connect to port 5678 on a Kubernetes node. In order to do this you need to create a tunnel that port forwards 5678 to your local machine.
+3 -3
View File
@@ -3,7 +3,7 @@ sidebar_position: 6
version: 1
---
# Miscellaneous
# Misc.
## Reporting a Security Vulnerability
@@ -11,7 +11,7 @@ Please report security vulnerabilities to private@superset.apache.org.
In the event a community member discovers a security flaw in Superset, it is important to follow the [Apache Security Guidelines](https://www.apache.org/security/committers.html) and release a fix as quickly as possible before public disclosure. Reporting security vulnerabilities through the usual GitHub Issues channel is not ideal as it will publicize the flaw before a fix can be applied.
## SQL Lab Async
### SQL Lab Async
It's possible to configure a local database to operate in `async` mode,
to work on `async` related features.
@@ -46,7 +46,7 @@ Note that:
to your production environment, and use the similar broker as well as
results backend configuration
## Async Chart Queries
### Async Chart Queries
It's possible to configure database queries for charts to operate in `async` mode. This is especially useful for dashboards with many charts that may otherwise be affected by browser connection limits. To enable async queries for dashboards and Explore, the following dependencies are required:
+1 -1
View File
@@ -125,7 +125,7 @@ value in milliseconds in the JSON Metadata field:
Here, the entire dashboard will refresh at once if periodic refresh is on. The stagger time of 2.5
seconds is ignored.
**Why does flask fab or superset freeze/hang/not responding when started (my home directory is
**Why does flask fab or superset freezed/hung/not responding when started (my home directory is
NFS mounted)?**
By default, Superset creates and uses an SQLite database at `~/.superset/superset.db`. SQLite is
+1 -1
View File
@@ -45,7 +45,7 @@ This is the core application. Superset operates like this:
This is where chart and dashboard definitions, user information, logs, etc. are stored. Superset is tested to work with PostgreSQL and MySQL databases as the metadata database (not be confused with a data source like your data warehouse, which could be a much greater variety of options like Snowflake, Redshift, etc.).
Some installation methods like our Quickstart and PyPI come configured by default to use a SQLite on-disk database. And in a Docker Compose installation, the data would be stored in a PostgreSQL container volume. Neither of these cases are recommended for production instances of Superset.
Some installation methods like our Quickstart and PyPI come configured by default to use a SQLite on-disk database. And in a Docker Compose installation, the data would be stored in a PostgresQL container volume. Neither of these cases are recommended for production instances of Superset.
For production, a properly-configured, managed, standalone database is recommended. No matter what database you use, you should plan to back it up regularly.
+2 -1
View File
@@ -17,7 +17,7 @@ Since `docker compose` is primarily designed to run a set of containers on **a s
and can't support requirements for **high availability**, we do not support nor recommend
using our `docker compose` constructs to support production-type use-cases. For single host
environments, we recommend using [minikube](https://minikube.sigs.k8s.io/docs/start/) along
with our [installing on k8s](https://superset.apache.org/docs/installation/running-on-kubernetes)
our [installing on k8s](https://superset.apache.org/docs/installation/running-on-kubernetes)
documentation.
:::
@@ -43,6 +43,7 @@ Note that there are 3 major ways we support to run `docker compose`:
`export TAG=4.0.0-dev` or `export TAG=3.0.0-dev`, with `latest-dev` being the default.
That's because The `dev` builds happen to package the `psycopg2-binary` required to connect
to the Postgres database launched as part of the `docker compose` builds.
``
More on these two approaches after setting up the requirements for either.
+3 -6
View File
@@ -150,9 +150,6 @@ Superset requires a Python DB-API database driver and a SQLAlchemy
dialect to be installed for each datastore you want to connect to.
See [Install Database Drivers](/docs/configuration/databases) for more information.
It is recommended that you refer to versions listed in
[pyproject.toml](https://github.com/apache/superset/blob/master/pyproject.toml)
instead of hard-coding them in your bootstrap script, as seen below.
:::
@@ -160,9 +157,9 @@ The following example installs the drivers for BigQuery and Elasticsearch, allow
```yaml
bootstrapScript: |
#!/bin/bash
uv pip install .[postgres] \
.[bigquery] \
.[elasticsearch] &&\
pip install psycopg2==2.9.6 \
sqlalchemy-bigquery==1.6.1 \
elasticsearch-dbapi==0.2.5 &&\
if [ ! -f ~/bootstrap ]; then echo "Running Superset with uid {{ .Values.runAsUser }}" > ~/bootstrap; fi
```
+1 -1
View File
@@ -115,7 +115,7 @@ the models and views they can access, and that Finance role that is a collection
A user can have multiple roles associated with them. For example, an executive on the Finance
team could be granted **Gamma**, **Finance**, and the **Executive** roles. The **Executive**
role could provide access to a set of data sources and dashboards made available only to executives.
In the **Dashboards** view, a user can only see the ones they have access to
In the **Dashboards** view, a user can only see the ones they have access too
based on the roles and permissions that were attributed.
### Row Level Security
@@ -17,13 +17,14 @@
* under the License.
*/
import type { Config } from '@docusaurus/types';
import type { Options, ThemeConfig } from '@docusaurus/preset-classic';
import { themes } from 'prism-react-renderer';
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const { github: lightCodeTheme, vsDark: darkCodeTheme } = themes;
const lightCodeTheme = require("prism-react-renderer").themes.github;
const darkCodeTheme = require("prism-react-renderer").themes.vsDark;
const config: Config = {
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'Superset',
tagline:
'Apache Superset is a modern data exploration and visualization platform',
@@ -32,8 +33,8 @@ const config: Config = {
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'throw',
favicon: '/img/favicon.ico',
organizationName: 'apache',
projectName: 'superset',
organizationName: 'apache', // Usually your GitHub org/user name.
projectName: 'superset', // Usually your repo name.
themes: ['@saucelabs/theme-github-codeblock'],
plugins: [
[
@@ -198,103 +199,105 @@ const config: Config = {
presets: [
[
'@docusaurus/preset-classic',
{
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
sidebarPath: require.resolve('./sidebars.js'),
editUrl: ({ versionDocsDirPath, docPath }) => {
editUrl:
({versionDocsDirPath, docPath}) => {
if (docPath === 'intro.md') {
return 'https://github.com/apache/superset/edit/master/README.md';
return 'https://github.com/apache/superset/edit/master/README.md'
}
return `https://github.com/apache/superset/edit/master/docs/${versionDocsDirPath}/${docPath}`;
},
return `https://github.com/apache/superset/edit/master/docs/${versionDocsDirPath}/${docPath}`
}
},
blog: {
showReadingTime: true,
// Please change this to your repo.
editUrl:
'https://github.com/facebook/docusaurus/edit/main/website/blog/',
editUrl: 'https://github.com/facebook/docusaurus/edit/main/website/blog/',
},
theme: {
customCss: require.resolve('./src/styles/custom.css'),
},
} satisfies Options,
}),
],
],
themeConfig: {
colorMode: {
defaultMode: 'dark',
disableSwitch: false,
respectPrefersColorScheme: true,
},
algolia: {
appId: 'WR5FASX5ED',
apiKey: 'd0d22810f2e9b614ffac3a73b26891fe',
indexName: 'superset-apache',
},
navbar: {
logo: {
alt: 'Superset Logo',
src: '/img/superset-logo-horiz.svg',
srcDark: '/img/superset-logo-horiz-dark.svg',
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
colorMode: {
defaultMode: 'light',
disableSwitch: true,
},
items: [
{
label: 'Documentation',
to: '/docs/intro',
items: [
{
label: 'Getting Started',
to: '/docs/intro',
},
{
label: 'FAQ',
to: '/docs/faq',
},
],
algolia: {
appId: 'WR5FASX5ED',
apiKey: 'd0d22810f2e9b614ffac3a73b26891fe',
indexName: 'superset-apache',
},
navbar: {
logo: {
alt: 'Superset Logo',
src: '/img/superset-logo-horiz.svg',
srcDark: '/img/superset-logo-horiz-dark.svg',
},
{
label: 'Community',
to: '/community',
items: [
{
label: 'Resources',
href: '/community',
},
{
label: 'GitHub',
href: 'https://github.com/apache/superset',
},
{
label: 'Slack',
href: 'http://bit.ly/join-superset-slack',
},
{
label: 'Mailing List',
href: 'https://lists.apache.org/list.html?dev@superset.apache.org',
},
{
label: 'Stack Overflow',
href: 'https://stackoverflow.com/questions/tagged/apache-superset',
},
],
},
{
href: '/docs/intro',
position: 'right',
className: 'default-button-theme get-started-button',
label: 'Get Started',
},
{
href: 'https://github.com/apache/superset',
position: 'right',
className: 'github-button',
},
],
},
footer: {
links: [],
copyright: `
items: [
{
label: 'Documentation',
to: '/docs/intro',
items: [
{
label: 'Getting Started',
to: '/docs/intro',
},
{
label: 'FAQ',
to: '/docs/faq',
},
],
},
{
label: 'Community',
to: '/community',
items: [
{
label: 'Resources',
href: '/community',
},
{
label: 'GitHub',
href: 'https://github.com/apache/superset',
},
{
label: 'Slack',
href: 'http://bit.ly/join-superset-slack',
},
{
label: 'Mailing List',
href: 'https://lists.apache.org/list.html?dev@superset.apache.org',
},
{
label: 'Stack Overflow',
href: 'https://stackoverflow.com/questions/tagged/apache-superset',
},
],
},
{
href: '/docs/intro',
position: 'right',
className: 'default-button-theme get-started-button',
label: 'Get Started',
},
{
href: 'https://github.com/apache/superset',
position: 'right',
className: 'github-button',
},
],
},
footer: {
links: [],
copyright: `
<div class="footer__applitools">
We use &nbsp;<a href="https://applitools.com/" target="_blank" rel="nofollow"><img src="/img/applitools.png" title="Applitools" /></a>
</div>
@@ -317,17 +320,17 @@ const config: Config = {
<!-- telemetry/analytics pixel: -->
<img referrerPolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=39ae6855-95fc-4566-86e5-360d542b0a68" />
`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
docs: {
sidebar: {
hideable: true,
},
},
} satisfies ThemeConfig,
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
docs: {
sidebar: {
hideable: true,
}
},
}),
scripts: [
'/script/matomo.js',
// {
@@ -337,4 +340,4 @@ const config: Config = {
],
};
export default config;
module.exports = config;
+16 -14
View File
@@ -14,40 +14,42 @@
"serve": "yarn run _init && docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "tsc",
"eslint": "eslint . --ext .js,.jsx,.ts,.tsx"
"typecheck": "tsc"
},
"dependencies": {
"@algolia/client-search": "^5.18.0",
"@ant-design/icons": "^5.5.2",
"@docsearch/react": "^3.8.2",
"@docusaurus/core": "^3.5.2",
"@docusaurus/plugin-client-redirects": "^3.5.2",
"@docusaurus/preset-classic": "^3.5.2",
"@emotion/core": "^10.1.1",
"@emotion/styled": "^10.0.27",
"@mdx-js/react": "^3.1.0",
"@saucelabs/theme-github-codeblock": "^0.3.0",
"@superset-ui/style": "^0.14.23",
"antd": "^5.24.1",
"@svgr/webpack": "^8.1.0",
"antd": "^5.22.7",
"buffer": "^6.0.3",
"clsx": "^2.1.1",
"docusaurus-plugin-less": "^2.0.2",
"less": "^4.2.2",
"file-loader": "^6.2.0",
"less": "^4.2.1",
"less-loader": "^11.0.0",
"prism-react-renderer": "^2.4.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-github-btn": "^1.4.0",
"react-svg-pan-zoom": "^3.13.1",
"swagger-ui-react": "^5.19.0"
"stream": "^0.0.3",
"swagger-ui-react": "^5.18.2",
"url-loader": "^4.1.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.7.0",
"@docusaurus/module-type-aliases": "^3.6.3",
"@docusaurus/tsconfig": "^3.6.3",
"@types/react": "^18.3.12",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.0",
"eslint-config-prettier": "^8.0.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.0.0",
"prettier": "^2.0.0",
"typescript": "~5.1.6",
"typescript": "^5.7.2",
"webpack": "^5.97.1"
},
"browserslist": {
+21 -31
View File
@@ -1,4 +1,3 @@
/* eslint-env node */
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -40,52 +39,42 @@ const sidebars = {
{
type: 'category',
label: 'Installation',
items: [
{
type: 'autogenerated',
dirName: 'installation',
},
],
items: [{
type: 'autogenerated',
dirName: 'installation',
}]
},
{
type: 'category',
label: 'Configuration',
items: [
{
type: 'autogenerated',
dirName: 'configuration',
},
],
items: [{
type: 'autogenerated',
dirName: 'configuration',
}]
},
{
type: 'category',
label: 'Using Superset',
items: [
{
type: 'autogenerated',
dirName: 'using-superset',
},
],
items: [{
type: 'autogenerated',
dirName: 'using-superset',
}]
},
{
type: 'category',
label: 'Contributing',
items: [
{
type: 'autogenerated',
dirName: 'contributing',
},
],
items: [{
type: 'autogenerated',
dirName: 'contributing',
}]
},
{
type: 'category',
label: 'Security',
items: [
{
type: 'autogenerated',
dirName: 'security',
},
],
items: [{
type: 'autogenerated',
dirName: 'security',
}]
},
{
type: 'doc',
@@ -98,6 +87,7 @@ const sidebars = {
id: 'api',
},
],
};
module.exports = sidebars;
+1 -1
View File
@@ -94,7 +94,7 @@ const StyledSectionHeaderH2 = styled(StyledSectionHeader)`
`;
interface SectionHeaderProps {
level: 'h1' | 'h2';
level: any;
title: string;
subtitle?: string | ReactNode;
dark?: boolean;
+24 -16
View File
@@ -86,7 +86,7 @@ const communityLinks = [
];
const StyledJoinCommunity = styled('section')`
background-color: var(--ifm-background-color);
background-color: var(--ifm-off-section-background);
border-bottom: 1px solid var(--ifm-border-color);
.list {
max-width: 540px;
@@ -118,7 +118,7 @@ const StyledJoinCommunity = styled('section')`
.description {
font-size: 14px;
line-height: 20px;
color: var(--ifm-font-base-color);
color: var(--ifm-secondary-text);
margin-top: -8px;
margin-bottom: 23px;
${mq[1]} {
@@ -143,6 +143,22 @@ const StyledCalendarIframe = styled('iframe')`
}
`;
const StyledNewsletterIframe = styled('iframe')`
display: block;
max-width: 1080px;
width: calc(100% - 40px);
height: 285px;
margin: 30px auto 20px;
border: 0;
@media (max-width: 1200px) {
height: 380px;
}
@media (max-width: 679px) {
height: 680px;
margin-top: 15px;
}
`;
const StyledLink = styled('a')`
display: inline-flex;
align-items: center;
@@ -166,9 +182,10 @@ const StyledLink = styled('a')`
const FinePrint = styled('div')`
font-size: 14px;
color: var(--ifm-secondary-text);
`;
`
const Community = () => {
const [showCalendar, setShowCalendar] = useState(false); // State to control calendar visibility
const toggleCalendar = () => {
@@ -201,17 +218,14 @@ const Community = () => {
className="title"
href={url}
target="_blank"
rel="noreferrer"
aria-label={ariaLabel}
>
<img className="icon" src={`/img/community/${image}`} />
</a>
}
title={
<a href={url} target="_blank" rel="noreferrer">
<p className="title" style={{ marginBottom: 0 }}>
{title}
</p>
<a className="title" href={url} target="_blank">
{title}
</a>
}
description={<p className="description">{description}</p>}
@@ -232,22 +246,16 @@ const Community = () => {
<StyledLink
href="https://calendar.google.com/calendar/u/0/r?cid=superset.committers@gmail.com"
target="_blank"
rel="noreferrer"
>
<img src="/img/calendar-icon.svg" alt="calendar-icon" />
Subscribe to the Superset Community Calendar
</StyledLink>
<br />
<StyledLink onClick={toggleCalendar}>
<img src="/img/calendar-icon.svg" alt="calendar-icon" />
<img src="/img/calendar-icon.svg" alt="calendar-icon" />
{showCalendar ? 'Hide Calendar' : 'Display Calendar*'}
</StyledLink>
{!showCalendar && (
<FinePrint>
<sup>*</sup>Clicking on this link will load and send data
from and to Google.
</FinePrint>
)}
{!showCalendar && <FinePrint><sup>*</sup>Clicking on this link will load and send data from and to Google.</FinePrint>}
</>
}
/>
+7 -4
View File
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
// @ts-nocheck
import { useRef, useState, useEffect } from 'react';
import Layout from '@theme/Layout';
import Link from '@docusaurus/Link';
@@ -28,6 +29,8 @@ import SectionHeader from '../components/SectionHeader';
import BlurredSection from '../components/BlurredSection';
import '../styles/main.less';
// @ts-ignore
const features = [
{
image: 'powerful-yet-easy.jpg',
@@ -204,6 +207,7 @@ const StyledFeaturesList = styled('ul')`
.item {
text-align: left;
border: 1px solid var(--ifm-border-color);
background-color: #ffffff;
border-radius: 10px;
overflow: hidden;
display: flex;
@@ -226,6 +230,7 @@ const StyledFeaturesList = styled('ul')`
}
.title {
font-size: 24px;
color: var(--ifm-primary-text);
margin: 10px 0 0;
${mq[1]} {
font-size: 23px;
@@ -235,6 +240,7 @@ const StyledFeaturesList = styled('ul')`
.description {
font-size: 17px;
line-height: 23px;
color: var(--ifm-secondary-text);
margin: 5px 0 0;
${mq[1]} {
font-size: 16px;
@@ -641,10 +647,7 @@ export default function Home(): JSX.Element {
</div>
</Carousel>
<video autoPlay muted controls loop>
<source
src="https://superset.staged.apache.org/superset-video-4k.mp4"
type="video/mp4"
/>
<source src="https://superset.staged.apache.org/superset-video-4k.mp4" type="video/mp4" />
</video>
</StyledSliderSection>
<StyledKeyFeatures>
-5
View File
@@ -137,9 +137,4 @@ export const Databases = [
href: 'https://www.denodo.com/',
imgName: 'denodo.png',
},
{
title: 'TDengine',
href: 'https://www.tdengine.com/',
imgName: 'tdengine.png',
},
];
-12
View File
@@ -69,15 +69,3 @@ ul.dropdown__menu svg {
--ifm-code-padding-vertical: 3px;
--ifm-code-padding-horizontal: 5px;
}
[data-theme='dark'] {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--ifm-font-base-color: #bbb5ac;
--ifm-border-color: #797063;
}
+2 -1
View File
@@ -114,6 +114,7 @@ a > span > svg {
.navbar {
font-size: 14px;
font-weight: 400;
background-color: #fff;
transition: all 0.5s;
.get-started-button {
@@ -189,7 +190,7 @@ a > span > svg {
.navbar .DocSearch {
--docsearch-text-color: #187384;
--docsearch-muted-color: #187384;
--docsearch-searchbox-background: var(--ifm-navbar-background-color);
--docsearch-searchbox-background: #fff;
border: 1px solid #187384;
border-radius: 10px;
+4 -8
View File
@@ -33,14 +33,14 @@ const EditPageLink = styled('a')`
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: 0.9;
bpx-shadow: 0 0 0 0 rgba(0,0,0,0); /* Smooth transition for hover effect */
scale: .9;
transition: all 0.3s;
transform-origin: bottom right;
&:hover {
background-color: #333;
box-shadow: 5px 5px 10px 0 rgba(0, 0, 0, 0.3);
box-shadow: 5px 5px 10px 0 rgba(0,0,0,0.3);
scale: 1;
}
`;
@@ -48,11 +48,7 @@ const EditPageLink = styled('a')`
export default function DocItemWrapper(props) {
return (
<>
<EditPageLink
href={props.content.metadata.editUrl}
target="_blank"
rel="noopener noreferrer"
>
<EditPageLink href={props.content.metadata.editUrl} target="_blank" rel="noopener noreferrer">
Edit this page on GitHub
</EditPageLink>
<DocItem {...props} />
+1 -1
View File
@@ -19,4 +19,4 @@
const breakpoints = [576, 768, 992, 1200];
export const mq = breakpoints.map(bp => `@media (max-width: ${bp}px)`);
export const mq = breakpoints.map((bp) => `@media (max-width: ${bp}px)`);
+2
View File
@@ -64,5 +64,7 @@ RewriteRule ^docs/installation/event-logging/$ /docs/configuration/event-logging
RewriteRule ^docs/databases.*$ /docs/configuration/databases [R=301,L]
RewriteRule ^docs/configuration/setup-ssh-tunneling$ /docs/configuration/networking-settings [R=301,L]
# pre-commit hooks documentation
RewriteRule ^docs/contributing/hooks-and-linting/$ /docs/contributing/development/#git-hooks-1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

-1
View File
@@ -1,4 +1,3 @@
/* eslint-env browser */
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
+1 -1
View File
@@ -1,6 +1,6 @@
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@docusaurus/tsconfig",
"extends": "@docusaurus/tsconfig/tsconfig.json",
"compilerOptions": {
"baseUrl": "."
},
+1749 -2922
View File
File diff suppressed because it is too large Load Diff
-46
View File
@@ -1,46 +0,0 @@
{
"name": "superset",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"@types/react-resizable": "^3.0.8",
"@types/react-syntax-highlighter": "^15.5.13"
}
},
"node_modules/@types/react": {
"version": "19.0.10",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz",
"integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
"dev": true,
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-resizable": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/@types/react-resizable/-/react-resizable-3.0.8.tgz",
"integrity": "sha512-Pcvt2eGA7KNXldt1hkhVhAgZ8hK41m0mp89mFgQi7LAAEZiaLgm4fHJ5zbJZ/4m2LVaAyYrrRRv1LHDcrGQanA==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-syntax-highlighter": {
"version": "15.5.13",
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
"integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true
}
}
}
-6
View File
@@ -1,6 +0,0 @@
{
"devDependencies": {
"@types/react-resizable": "^3.0.8",
"@types/react-syntax-highlighter": "^15.5.13"
}
}
+6 -10
View File
@@ -41,7 +41,7 @@ dependencies = [
"colorama",
"croniter>=0.3.28",
"cron-descriptor",
"cryptography>=42.0.4, <45.0.0",
"cryptography>=42.0.4, <44.0.0",
"deprecation>=2.1.0, <2.2.0",
"flask>=2.2.5, <3.0.0",
"flask-appbuilder>=4.5.3, <5.0.0",
@@ -71,7 +71,7 @@ dependencies = [
# --------------------------
# pandas and related (wanting pandas[performance] without numba as it's 100+MB and not needed)
"pandas[excel]>=2.0.3, <2.1",
"bottleneck", # recommended performance dependency for pandas, see https://pandas.pydata.org/docs/getting_started/install.html#performance-dependencies-recommended
"bottleneck",
# --------------------------
"parsedatetime",
"paramiko>=3.4.0",
@@ -79,7 +79,7 @@ dependencies = [
"polyline>=2.0.0, <3.0",
"pyparsing>=3.0.6, <4",
"python-dateutil",
"python-dotenv", # optional dependencies for Flask but required for Superset, see https://flask.palletsprojects.com/en/stable/installation/#optional-dependencies
"python-dotenv",
"python-geohash",
"pyarrow>=14.0.1, <15",
"pyyaml>=6.0.0, <7.0.0",
@@ -87,6 +87,7 @@ dependencies = [
"redis>=4.6.0, <5.0",
"selenium>=4.14.0, <5.0",
"shillelagh[gsheetsapi]>=1.2.18, <2.0",
"shortid",
"sshtunnel>=0.4.0, <0.5",
"simplejson>=3.15.0",
"slack_sdk>=3.19.0, <4",
@@ -113,7 +114,7 @@ bigquery = [
]
clickhouse = ["clickhouse-connect>=0.5.14, <1.0"]
cockroachdb = ["cockroachdb>=0.3.5, <0.4"]
cors = ["flask-cors>=4.0.2, <5.0"]
cors = ["flask-cors>=2.0.0"]
crate = ["sqlalchemy-cratedb>=0.40.1, <1"]
databend = ["databend-sqlalchemy>=0.3.2, <1.0"]
databricks = [
@@ -125,7 +126,7 @@ denodo = ["denodo-sqlalchemy~=1.0.6"]
dremio = ["sqlalchemy-dremio>=1.2.1, <4"]
drill = ["sqlalchemy-drill>=1.1.4, <2"]
druid = ["pydruid>=0.6.5,<0.7"]
duckdb = ["duckdb-engine>=0.10", "duckdb>=1.1.0"]
duckdb = ["duckdb-engine>=0.9.5, <0.10"]
dynamodb = ["pydynamodb>=0.4.2"]
solr = ["sqlalchemy-solr >= 0.2.0"]
elasticsearch = ["elasticsearch-dbapi>=0.2.9, <0.3.0"]
@@ -155,7 +156,6 @@ ocient = [
"geojson",
]
oracle = ["cx-Oracle>8.0.0, <8.1"]
parseable = ["sqlalchemy-parseable>=0.1.3,<0.2.0"]
pinot = ["pinotdb>=5.0.0, <6.0.0"]
playwright = ["playwright>=1.37.0, <2"]
postgres = ["psycopg2-binary==2.9.6"]
@@ -172,10 +172,6 @@ spark = [
"tableschema",
"thrift>=0.14.1, <1",
]
tdengine = [
"taospy>=2.7.21",
"taos-ws-py>=0.3.8"
]
teradata = ["teradatasql>=16.20.0.23"]
thumbnails = ["Pillow>=10.0.1, <11"]
vertica = ["sqlalchemy-vertica-python>=0.5.9, < 0.6"]
+1 -1
View File
@@ -16,7 +16,7 @@
# specific language governing permissions and limitations
# under the License.
#
urllib3>=1.26.19, <2.0.0
urllib3>=1.26.18
werkzeug>=3.0.1
numexpr>=2.9.0
+5 -3
View File
@@ -173,7 +173,7 @@ itsdangerous==2.2.0
# via
# flask
# flask-wtf
jinja2==3.1.5
jinja2==3.1.4
# via
# flask
# flask-babel
@@ -252,7 +252,7 @@ parsedatetime==2.6
# via apache-superset (pyproject.toml)
pgsanity==0.2.9
# via apache-superset (pyproject.toml)
platformdirs==3.9.1
platformdirs==3.8.1
# via requests-cache
ply==3.11
# via jsonpath-ng
@@ -329,6 +329,8 @@ selenium==4.27.1
# via apache-superset (pyproject.toml)
shillelagh==1.2.18
# via apache-superset (pyproject.toml)
shortid==0.1.2
# via apache-superset (pyproject.toml)
simplejson==3.19.3
# via apache-superset (pyproject.toml)
six==1.16.0
@@ -385,7 +387,7 @@ tzdata==2024.2
# pandas
url-normalize==1.4.3
# via requests-cache
urllib3==1.26.20
urllib3==1.26.18
# via
# -r requirements/base.in
# requests
+10 -6
View File
@@ -206,7 +206,7 @@ flask-compress==1.17
# via
# -c requirements/base.txt
# apache-superset
flask-cors==4.0.2
flask-cors==4.0.0
# via apache-superset
flask-jwt-extended==4.7.1
# via
@@ -362,7 +362,7 @@ itsdangerous==2.2.0
# -c requirements/base.txt
# flask
# flask-wtf
jinja2==3.1.5
jinja2==3.1.4
# via
# -c requirements/base.txt
# flask
@@ -530,7 +530,7 @@ pillow==10.3.0
# via
# apache-superset
# matplotlib
platformdirs==3.9.1
platformdirs==3.8.1
# via
# -c requirements/base.txt
# requests-cache
@@ -545,7 +545,7 @@ polyline==2.0.2
# via
# -c requirements/base.txt
# apache-superset
pre-commit==4.1.0
pre-commit==4.0.1
# via apache-superset
prison==0.2.1
# via
@@ -738,6 +738,10 @@ shillelagh==1.2.18
# via
# -c requirements/base.txt
# apache-superset
shortid==0.1.2
# via
# -c requirements/base.txt
# apache-superset
simplejson==3.19.3
# via
# -c requirements/base.txt
@@ -836,7 +840,7 @@ url-normalize==1.4.3
# via
# -c requirements/base.txt
# requests-cache
urllib3==1.26.20
urllib3==1.26.18
# via
# -c requirements/base.txt
# docker
@@ -849,7 +853,7 @@ vine==5.1.0
# amqp
# celery
# kombu
virtualenv==20.29.2
virtualenv==20.23.1
# via pre-commit
wcwidth==0.2.13
# via
+1 -1
View File
@@ -217,7 +217,7 @@ def cancel_github_workflows( # noqa: C901
seen = set()
dups = []
for item in reversed(runs):
key = f"{item['event']}_{item['head_branch']}_{item['workflow_id']}"
key = f'{item["event"]}_{item["head_branch"]}_{item["workflow_id"]}'
if key in seen:
dups.append(item)
else:
-260
View File
@@ -1,260 +0,0 @@
#!/usr/bin/env node
/**
* 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.
*/
// @ts-check
const { exit } = require("node:process");
const { join, dirname, normalize, sep } = require("node:path");
const { readdir, stat } = require("node:fs/promises");
const { existsSync } = require("node:fs");
const { chdir, cwd } = require("node:process");
const { createRequire } = require("node:module");
const SUPERSET_ROOT = dirname(__dirname);
const PACKAGE_ARG_REGEX = /^package=/;
const EXCLUDE_DECLARATION_DIR_REGEX = /^excludeDeclarationDir=/;
const DECLARATION_FILE_REGEX = /\.d\.ts$/;
void (async () => {
const args = process.argv.slice(2);
const {
matchedArgs: [packageArg, excludeDeclarationDirArg],
remainingArgs,
} = extractArgs(args, [PACKAGE_ARG_REGEX, EXCLUDE_DECLARATION_DIR_REGEX]);
if (!packageArg) {
console.error("package is not specified");
exit(1);
}
const packageRootDir = await getPackage(packageArg);
const updatedArgs = removePackageSegment(remainingArgs, packageRootDir);
const argsStr = updatedArgs.join(" ");
const excludedDeclarationDirs = getExcludedDeclarationDirs(
excludeDeclarationDirArg
);
let declarationFiles = await getFilesRecursively(
packageRootDir,
DECLARATION_FILE_REGEX,
excludedDeclarationDirs
);
declarationFiles = removePackageSegment(declarationFiles, packageRootDir);
const declarationFilesStr = declarationFiles.join(" ");
const packageRootDirAbsolute = join(SUPERSET_ROOT, packageRootDir);
const tsConfig = getTsConfig(packageRootDirAbsolute);
const command = `--noEmit --allowJs --composite false --project ${tsConfig} ${argsStr} ${declarationFilesStr}`;
try {
chdir(packageRootDirAbsolute);
// Please ensure that tscw-config is installed in the package being type-checked.
const tscw = packageRequire("tscw-config");
const child = await tscw`${command}`;
if (child.stdout) {
console.log(child.stdout);
}
if (child.stderr) {
console.error(child.stderr);
}
exit(child.exitCode);
} catch (e) {
console.error("Failed to execute type checking:", e);
console.error("Package:", packageRootDir);
console.error("Command:", `tscw ${command}`);
exit(1);
}
})();
/**
*
* @param {string} fullPath
* @param {string[]} excludedDirs
*/
function shouldExcludeDir(fullPath, excludedDirs) {
return excludedDirs.some((excludedDir) => {
const normalizedExcludedDir = normalize(excludedDir);
const normalizedPath = normalize(fullPath);
return (
normalizedExcludedDir === normalizedPath ||
normalizedPath
.split(sep)
.filter((segment) => segment)
.includes(normalizedExcludedDir)
);
});
}
/**
* @param {string} dir
* @param {RegExp} regex
* @param {string[]} excludedDirs
*
* @returns {Promise<string[]>}
*/
async function getFilesRecursively(dir, regex, excludedDirs) {
try {
const files = await readdir(dir, { withFileTypes: true });
const recursivePromises = [];
const result = [];
for (const file of files) {
const fullPath = join(dir, file.name);
if (file.isDirectory() && !shouldExcludeDir(fullPath, excludedDirs)) {
recursivePromises.push(
getFilesRecursively(fullPath, regex, excludedDirs)
);
} else if (regex.test(file.name)) {
result.push(fullPath);
}
}
const recursiveResults = await Promise.all(recursivePromises);
return result.concat(...recursiveResults);
} catch (e) {
console.error(`Error reading directory: ${dir}`);
console.error(e);
exit(1);
}
}
/**
*
* @param {string} packageArg
* @returns {Promise<string>}
*/
async function getPackage(packageArg) {
const packageDir = packageArg.split("=")[1].replace(/\/$/, "");
try {
const stats = await stat(packageDir);
if (!stats.isDirectory()) {
console.error(
`Please specify a valid package, ${packageDir} is not a directory.`
);
exit(1);
}
} catch (e) {
console.error(`Error reading package: ${packageDir}`);
console.error(e);
exit(1);
}
return packageDir;
}
/**
*
* @param {string | undefined} excludeDeclarationDirArg
* @returns {string[]}
*/
function getExcludedDeclarationDirs(excludeDeclarationDirArg) {
const excludedDirs = ["node_modules"];
return !excludeDeclarationDirArg
? excludedDirs
: excludeDeclarationDirArg
.split("=")[1]
.split(",")
.map((dir) => dir.replace(/\/$/, "").trim())
.concat(excludedDirs);
}
/**
*
* @param {string[]} args
* @param {RegExp[]} regexes
* @returns {{ matchedArgs: (string | undefined)[], remainingArgs: string[] }}
*/
function extractArgs(args, regexes) {
/**
* @type {(string | undefined)[]}
*/
const matchedArgs = [];
const remainingArgs = [...args];
regexes.forEach((regex) => {
const index = remainingArgs.findIndex((arg) => regex.test(arg));
if (index !== -1) {
const [arg] = remainingArgs.splice(index, 1);
matchedArgs.push(arg);
} else {
matchedArgs.push(undefined);
}
});
return { matchedArgs, remainingArgs };
}
/**
* Remove the package segment from path.
*
* For example: `superset-frontend/foo/bar.ts` -> `foo/bar.ts`
*
* @param {string[]} args
* @param {string} package
* @returns {string[]}
*/
function removePackageSegment(args, package) {
const packageSegment = package.concat(sep);
return args.map((arg) => {
const normalizedPath = normalize(arg);
if (normalizedPath.startsWith(packageSegment)) {
return normalizedPath.slice(packageSegment.length);
}
return arg;
});
}
/**
*
* @param {string} dir
*/
function getTsConfig(dir) {
const defaultTsConfig = "tsconfig.json";
const tsConfig = join(dir, defaultTsConfig);
if (!existsSync(tsConfig)) {
console.error(`Error: ${defaultTsConfig} not found in ${dir}`);
exit(1);
}
return tsConfig;
}
/**
*
* @param {string} module
*/
function packageRequire(module) {
try {
const localRequire = createRequire(join(cwd(), "node_modules"));
return localRequire(module);
} catch (e) {
console.error(
`Error: ${module} is not installed in ${cwd()}. Please install it first.`
);
exit(1);
}
}
-30
View File
@@ -1,30 +0,0 @@
#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -e
script_dir="$(dirname "$(realpath "$0")")"
root_dir="$(dirname "$script_dir")"
frontend_dir=superset-frontend
if [[ ! -d "$root_dir/$frontend_dir" ]]; then
echo "Error: $frontend_dir directory not found in $root_dir" >&2
exit 1
fi
cd "$root_dir/$frontend_dir"
npm run eslint -- "${@//$frontend_dir\//}"
-1
View File
@@ -1 +0,0 @@
v20.16.0
+2 -49
View File
@@ -1,12 +1,12 @@
{
"name": "@superset-ui/embedded-sdk",
"version": "0.1.3",
"version": "0.1.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@superset-ui/embedded-sdk",
"version": "0.1.3",
"version": "0.1.2",
"license": "Apache-2.0",
"dependencies": {
"@superset-ui/switchboard": "^0.20.3",
@@ -22,7 +22,6 @@
"axios": "^1.7.7",
"babel-loader": "^9.1.3",
"jest": "^29.7.0",
"tscw-config": "^1.1.2",
"typescript": "^5.6.2",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4"
@@ -7801,35 +7800,6 @@
"node": ">=8.0"
}
},
"node_modules/tscw-config": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/tscw-config/-/tscw-config-1.1.2.tgz",
"integrity": "sha512-mrrMxCqC6kjqjuhGc7mTOB3P7JuBebZ0ZnFQTi4e+K0K+2kT1OvTXzFygWCPBor/F8WJ1IWVRrnBLKctFhFwOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"strip-json-comments": "^5.0.1"
},
"bin": {
"tscw": "dist/cli.js"
},
"peerDependencies": {
"typescript": ">=2.0.0"
}
},
"node_modules/tscw-config/node_modules/strip-json-comments": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz",
"integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@@ -13856,23 +13826,6 @@
"is-number": "^7.0.0"
}
},
"tscw-config": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/tscw-config/-/tscw-config-1.1.2.tgz",
"integrity": "sha512-mrrMxCqC6kjqjuhGc7mTOB3P7JuBebZ0ZnFQTi4e+K0K+2kT1OvTXzFygWCPBor/F8WJ1IWVRrnBLKctFhFwOQ==",
"dev": true,
"requires": {
"strip-json-comments": "^5.0.1"
},
"dependencies": {
"strip-json-comments": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz",
"integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==",
"dev": true
}
}
},
"type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
-1
View File
@@ -46,7 +46,6 @@
"axios": "^1.7.7",
"babel-loader": "^9.1.3",
"jest": "^29.7.0",
"tscw-config": "^1.1.2",
"typescript": "^5.6.2",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4"
+3 -11
View File
@@ -74,7 +74,7 @@ module.exports = {
'file-progress',
'lodash',
'theme-colors',
'i18n-strings',
'translation-vars',
'react-prefer-function-component',
'prettier',
],
@@ -284,7 +284,7 @@ module.exports = {
],
rules: {
'theme-colors/no-literal-colors': 0,
'i18n-strings/no-template-vars': 0,
'translation-vars/no-template-vars': 0,
'no-restricted-imports': 0,
'react/no-void-elements': 0,
},
@@ -292,7 +292,7 @@ module.exports = {
],
rules: {
'theme-colors/no-literal-colors': 'error',
'i18n-strings/no-template-vars': ['error', true],
'translation-vars/no-template-vars': ['error', true],
camelcase: [
'error',
{
@@ -354,14 +354,6 @@ module.exports = {
name: 'lodash/memoize',
message: 'Lodash Memoize is unsafe! Please use memoize-one instead',
},
{
name: '@testing-library/react',
message: 'Please use spec/helpers/testing-library instead',
},
{
name: '@testing-library/react-dom-utils',
message: 'Please use spec/helpers/testing-library instead',
},
],
patterns: ['antd/*'],
},
@@ -47,12 +47,12 @@ describe.skip('Dashboard top-level controls', () => {
// Solution: pause the network before clicking, assert, then unpause network.
cy.get('[data-test="refresh-chart-menu-item"]').should(
'have.class',
'antd5-dropdown-menu-item-disabled',
'ant-dropdown-menu-item-disabled',
);
waitForChartLoad(mapSpec);
cy.get('[data-test="refresh-chart-menu-item"]').should(
'not.have.class',
'antd5-dropdown-menu-item-disabled',
'ant-dropdown-menu-item-disabled',
);
});
});
@@ -65,7 +65,7 @@ describe.skip('Dashboard top-level controls', () => {
cy.get('[aria-label="more-horiz"]').click();
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
'not.have.class',
'antd5-dropdown-menu-item-disabled',
'ant-dropdown-menu-item-disabled',
);
cy.get('[data-test="refresh-dashboard-menu-item"]').click({
@@ -73,7 +73,7 @@ describe.skip('Dashboard top-level controls', () => {
});
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
'have.class',
'antd5-dropdown-menu-item-disabled',
'ant-dropdown-menu-item-disabled',
);
// wait all charts force refreshed.
@@ -94,7 +94,7 @@ describe.skip('Dashboard top-level controls', () => {
cy.get('[aria-label="more-horiz"]').click();
cy.get('[data-test="refresh-dashboard-menu-item"]').and(
'not.have.class',
'antd5-dropdown-menu-item-disabled',
'ant-dropdown-menu-item-disabled',
);
});
});
@@ -54,14 +54,15 @@ const drillBy = (targetDrillByColumn: string, isLegacy = false) => {
interceptV1ChartData();
}
cy.get('.antd5-dropdown:not(.antd5-dropdown-hidden)')
cy.get('.ant-dropdown:not(.ant-dropdown-hidden)')
.first()
.should('be.visible')
.find("[role='menu'] [role='menuitem']")
.contains(/^Drill by$/)
.trigger('mouseover', { force: true });
cy.get(
'.antd5-dropdown-menu-submenu:not(.antd5-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
)
.should('be.visible')
.find('[role="menuitem"]')
@@ -61,14 +61,15 @@ function drillToDetail(targetMenuItem: string) {
const drillToDetailBy = (targetDrill: string) => {
interceptSamples();
cy.get('.antd5-dropdown:not(.antd5-dropdown-hidden)')
cy.get('.ant-dropdown:not(.ant-dropdown-hidden)')
.first()
.should('be.visible')
.find("[role='menu'] [role='menuitem']")
.contains(/^Drill to detail by$/)
.trigger('mouseover', { force: true });
cy.get(
'.antd5-dropdown-menu-submenu:not(.antd5-dropdown-menu-submenu-hidden) [data-test="drill-to-detail-by-submenu"]',
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-to-detail-by-submenu"]',
)
.should('be.visible')
.find('[role="menuitem"]')
@@ -57,16 +57,16 @@ function setFilterBarOrientation(orientation: 'vertical' | 'horizontal') {
.trigger('mouseover');
if (orientation === 'vertical') {
cy.get('.antd5-dropdown-menu-item-selected')
cy.get('.antd5-menu-item-selected')
.contains('Horizontal (Top)')
.should('exist');
cy.get('.antd5-dropdown-menu-item').contains('Vertical (Left)').click();
cy.get('.antd5-menu-item').contains('Vertical (Left)').click();
cy.getBySel('dashboard-filters-panel').should('exist');
} else {
cy.get('.antd5-dropdown-menu-item-selected')
cy.get('.antd5-menu-item-selected')
.contains('Vertical (Left)')
.should('exist');
cy.get('.antd5-dropdown-menu-item').contains('Horizontal (Top)').click();
cy.get('.antd5-menu-item').contains('Horizontal (Top)').click();
cy.getBySel('loading-indicator').should('exist');
cy.getBySel('filter-bar').should('exist');
cy.getBySel('dashboard-filters-panel').should('not.exist');
@@ -161,7 +161,7 @@ describe('Horizontal FilterBar', () => {
cy.getBySel('filter-control-name')
.contains('test_12')
.should('not.be.visible');
cy.get('.antd5-popover-inner').scrollTo('bottom');
cy.get('.ant-popover-inner-content').scrollTo('bottom');
cy.getBySel('filter-control-name').contains('test_12').should('be.visible');
});
@@ -226,7 +226,7 @@ describe('Horizontal FilterBar', () => {
cy.getBySel('slice-header').within(() => {
cy.get('.filter-counts').trigger('mouseover');
});
cy.getBySel('filter-status-popover').contains('test_9').click();
cy.get('.filterStatusPopover').contains('test_9').click();
cy.getBySel('dropdown-content').should('be.visible');
cy.get('.ant-select-focused').should('be.visible');
});
@@ -456,19 +456,19 @@ export function applyAdvancedTimeRangeFilterOnDashboard(
endRange?: string,
) {
cy.get('.control-label').contains('RANGE TYPE').should('be.visible');
cy.get('.antd5-popover-content .ant-select-selector')
cy.get('.ant-popover-content .ant-select-selector')
.should('be.visible')
.click();
cy.get(`[label="Advanced"]`).should('be.visible').click();
cy.get('.section-title').contains('Advanced Time Range').should('be.visible');
if (startRange) {
cy.get('.antd5-popover-inner-content')
cy.get('.ant-popover-inner-content')
.find('[class^=ant-input]')
.first()
.type(`${startRange}`);
}
if (endRange) {
cy.get('.antd5-popover-inner-content')
cy.get('.ant-popover-inner-content')
.find('[class^=ant-input]')
.last()
.type(`${endRange}`);
@@ -31,35 +31,35 @@ const SAMPLE_DASHBOARDS_INDEXES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function openDashboardsAddedTo() {
cy.getBySel('actions-trigger').click();
cy.get('.antd5-dropdown-menu-submenu-title')
cy.get('.ant-dropdown-menu-submenu-title')
.contains('On dashboards')
.trigger('mouseover', { force: true });
}
function closeDashboardsAddedTo() {
cy.get('.antd5-dropdown-menu-submenu-title')
cy.get('.ant-dropdown-menu-submenu-title')
.contains('On dashboards')
.trigger('mouseout', { force: true });
cy.getBySel('actions-trigger').click();
}
function verifyDashboardsSubmenuItem(dashboardName) {
cy.get('.antd5-dropdown-menu-submenu-popup').contains(dashboardName);
cy.get('.ant-dropdown-menu-submenu-popup').contains(dashboardName);
closeDashboardsAddedTo();
}
function verifyDashboardSearch() {
openDashboardsAddedTo();
cy.get('.antd5-dropdown-menu-submenu-popup').trigger('mouseover');
cy.get('.antd5-dropdown-menu-submenu-popup')
cy.get('.ant-dropdown-menu-submenu-popup').trigger('mouseover');
cy.get('.ant-dropdown-menu-submenu-popup')
.find('input[placeholder="Search"]')
.type('1');
cy.get('.antd5-dropdown-menu-submenu-popup').contains('1 - Sample dashboard');
cy.get('.antd5-dropdown-menu-submenu-popup')
cy.get('.ant-dropdown-menu-submenu-popup').contains('1 - Sample dashboard');
cy.get('.ant-dropdown-menu-submenu-popup')
.find('input[placeholder="Search"]')
.type('Blahblah');
cy.get('.antd5-dropdown-menu-submenu-popup').contains('No results found');
cy.get('.antd5-dropdown-menu-submenu-popup')
cy.get('.ant-dropdown-menu-submenu-popup').contains('No results found');
cy.get('.ant-dropdown-menu-submenu-popup')
.find('[aria-label="close-circle"]')
.click();
closeDashboardsAddedTo();
@@ -68,8 +68,8 @@ function verifyDashboardSearch() {
function verifyDashboardLink() {
interceptDashboardGet();
openDashboardsAddedTo();
cy.get('.antd5-dropdown-menu-submenu-popup').trigger('mouseover');
cy.get('.antd5-dropdown-menu-submenu-popup a')
cy.get('.ant-dropdown-menu-submenu-popup').trigger('mouseover');
cy.get('.ant-dropdown-menu-submenu-popup a')
.first()
.invoke('removeAttr', 'target')
.click();
@@ -51,8 +51,8 @@ describe('Datasource control', () => {
)
.first()
.focus();
cy.focused().clear({ force: true });
cy.focused().type(`${newMetricName}{enter}`, { force: true });
cy.focused().clear();
cy.focused().type(`${newMetricName}{enter}`);
cy.get('[data-test="datasource-modal-save"]').click();
cy.get('.antd5-modal-confirm-btns button').contains('OK').click();
@@ -36,10 +36,10 @@ describe('Download Chart > Bar chart', () => {
};
cy.visitChartByParams(formData);
cy.get('.header-with-actions .antd5-dropdown-trigger').click();
cy.get(':nth-child(3) > .antd5-dropdown-menu-submenu-title').click();
cy.get('.header-with-actions .ant-dropdown-trigger').click();
cy.get(':nth-child(3) > .ant-dropdown-menu-submenu-title').click();
cy.get(
'.antd5-dropdown-menu-submenu > .antd5-dropdown-menu li:nth-child(3)',
'.ant-dropdown-menu-submenu > .ant-dropdown-menu li:nth-child(3)',
).click();
cy.verifyDownload('.jpg', {
contains: true,
@@ -80,9 +80,9 @@ describe('SqlLab query tabs', () => {
// configure some editor settings
cy.get(editorInput).type('some random query string', { force: true });
cy.get(queryLimitSelector).parent().click({ force: true });
cy.get('.antd5-dropdown-menu')
cy.get('.ant-dropdown-menu')
.last()
.find('.antd5-dropdown-menu-item')
.find('.ant-dropdown-menu-item')
.first()
.click({ force: true });
@@ -158,10 +158,10 @@ export const sqlLabView = {
runButton: '.css-d3dxop',
},
rowsLimit: {
dropdown: '.antd5-dropdown-menu',
limitButton: '.antd5-dropdown-menu-item',
dropdown: '.ant-dropdown-menu',
limitButton: '.ant-dropdown-menu-item',
limitButtonText: '.css-151uxnz',
limitTextWithValue: '[class="antd5-dropdown-trigger"]',
limitTextWithValue: '[class="ant-dropdown-trigger"]',
},
renderedTableHeader: '.ReactVirtualized__Table__headerRow',
renderedTableRow: '.ReactVirtualized__Table__row',
@@ -555,7 +555,7 @@ export const exploreView = {
timeSection: {
timeRangeFilter: dataTestLocator('time-range-trigger'),
timeRangeFilterModal: {
container: '.antd5-popover-content',
container: '.ant-popover-content',
footer: '.footer',
cancelButton: dataTestLocator('cancel-button'),
configureLastTimeRange: {
@@ -633,7 +633,7 @@ export const dashboardView = {
refreshChart: dataTestLocator('refresh-chart-menu-item'),
},
threeDotsMenuIcon:
'.header-with-actions .right-button-panel .antd5-dropdown-trigger',
'.header-with-actions .right-button-panel .ant-dropdown-trigger',
threeDotsMenuDropdown: dataTestLocator('header-actions-menu'),
refreshDashboard: dataTestLocator('refresh-dashboard-menu-item'),
saveAsMenuOption: dataTestLocator('save-as-menu-item'),
+15 -86
View File
@@ -24,8 +24,7 @@
"devDependencies": {
"@types/querystringify": "^2.0.0",
"cypress": "^11.2.0",
"eslint-plugin-cypress": "^3.5.0",
"tscw-config": "^1.1.2"
"eslint-plugin-cypress": "^3.5.0"
}
},
"node_modules/@ampproject/remapping": {
@@ -5063,10 +5062,9 @@
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"engines": {
"node": ">= 0.6"
}
@@ -7542,12 +7540,11 @@
}
},
"node_modules/light-my-request": {
"version": "5.14.0",
"resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.14.0.tgz",
"integrity": "sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==",
"license": "BSD-3-Clause",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.13.0.tgz",
"integrity": "sha512-9IjUN9ZyCS9pTG+KqTDEQo68Sui2lHsYBrfMyVUTTZ3XhH8PMZq7xO94Kr+eP9dhi/kcKsx4N41p2IXEBil1pQ==",
"dependencies": {
"cookie": "^0.7.0",
"cookie": "^0.6.0",
"process-warning": "^3.0.0",
"set-cookie-parser": "^2.4.1"
}
@@ -10298,35 +10295,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/tscw-config": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/tscw-config/-/tscw-config-1.1.2.tgz",
"integrity": "sha512-mrrMxCqC6kjqjuhGc7mTOB3P7JuBebZ0ZnFQTi4e+K0K+2kT1OvTXzFygWCPBor/F8WJ1IWVRrnBLKctFhFwOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"strip-json-comments": "^5.0.1"
},
"bin": {
"tscw": "dist/cli.js"
},
"peerDependencies": {
"typescript": ">=2.0.0"
}
},
"node_modules/tscw-config/node_modules/strip-json-comments": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz",
"integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@@ -10385,21 +10353,6 @@
"is-typedarray": "^1.0.0"
}
},
"node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
@@ -14831,9 +14784,9 @@
}
},
"cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="
},
"core-js-compat": {
"version": "3.30.1",
@@ -16692,11 +16645,11 @@
}
},
"light-my-request": {
"version": "5.14.0",
"resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.14.0.tgz",
"integrity": "sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.13.0.tgz",
"integrity": "sha512-9IjUN9ZyCS9pTG+KqTDEQo68Sui2lHsYBrfMyVUTTZ3XhH8PMZq7xO94Kr+eP9dhi/kcKsx4N41p2IXEBil1pQ==",
"requires": {
"cookie": "^0.7.0",
"cookie": "^0.6.0",
"process-warning": "^3.0.0",
"set-cookie-parser": "^2.4.1"
}
@@ -18649,23 +18602,6 @@
"resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz",
"integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g=="
},
"tscw-config": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/tscw-config/-/tscw-config-1.1.2.tgz",
"integrity": "sha512-mrrMxCqC6kjqjuhGc7mTOB3P7JuBebZ0ZnFQTi4e+K0K+2kT1OvTXzFygWCPBor/F8WJ1IWVRrnBLKctFhFwOQ==",
"dev": true,
"requires": {
"strip-json-comments": "^5.0.1"
},
"dependencies": {
"strip-json-comments": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz",
"integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==",
"dev": true
}
}
},
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@@ -18712,13 +18648,6 @@
"is-typedarray": "^1.0.0"
}
},
"typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"devOptional": true,
"peer": true
},
"undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+1 -2
View File
@@ -31,7 +31,6 @@
"devDependencies": {
"@types/querystringify": "^2.0.0",
"cypress": "^11.2.0",
"eslint-plugin-cypress": "^3.5.0",
"tscw-config": "^1.1.2"
"eslint-plugin-cypress": "^3.5.0"
}
}
-1
View File
@@ -75,5 +75,4 @@ module.exports = {
},
],
],
testTimeout: 10000,
};
+3670 -166
View File
File diff suppressed because it is too large Load Diff
+12 -11
View File
@@ -48,10 +48,10 @@
"cover": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=4096\" jest --coverage",
"dev": "webpack --mode=development --color --watch",
"dev-server": "cross-env NODE_ENV=development BABEL_ENV=development node --max_old_space_size=4096 ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --mode=development",
"eslint": "eslint --ignore-path=.eslintignore --ext .js,.jsx,.ts,tsx --quiet",
"eslint": "eslint --ignore-path=.eslintignore --ext .js,.jsx,.ts,tsx",
"format": "npm run _prettier -- --write",
"lint": "npm run eslint -- . && npm run type",
"lint-fix": "npm run eslint -- . --fix",
"lint-fix": "npm run eslint -- . --fix --quiet",
"lint-stats": "eslint -f ./scripts/eslint-metrics-uploader.js --ignore-path=.eslintignore --ext .js,.jsx,.ts,.tsx . ",
"plugins:build": "node ./scripts/build.js",
"plugins:build-assets": "node ./scripts/copyAssets.js",
@@ -68,8 +68,8 @@
"prod": "npm run build",
"prune": "rm -rf ./{packages,plugins}/*/{node_modules,lib,esm,tsconfig.tsbuildinfo,package-lock.json} ./.temp_cache",
"storybook": "cross-env NODE_ENV=development BABEL_ENV=development storybook dev -p 6006",
"tdd": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --watch",
"test": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80% --silent",
"tdd": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=4096\" jest --watch",
"test": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=4096\" jest --max-workers=50%",
"type": "tsc --noEmit",
"update-maps": "jupyter nbconvert --to notebook --execute --inplace 'plugins/legacy-plugin-chart-country-map/scripts/Country Map GeoJSON Generator.ipynb' -Xfrozen_modules=off",
"validate-release": "../RELEASING/validate_this_release.sh"
@@ -139,7 +139,6 @@
"dom-to-pdf": "^0.3.2",
"echarts": "^5.6.0",
"emotion-rgba": "0.0.12",
"eslint-plugin-i18n-strings": "file:eslint-rules/eslint-plugin-i18n-strings",
"fast-glob": "^3.3.2",
"fs-extra": "^11.2.0",
"fuse.js": "^7.0.0",
@@ -230,7 +229,7 @@
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-modules-commonjs": "^7.26.3",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/preset-env": "^7.26.7",
"@babel/preset-env": "^7.26.0",
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.26.0",
"@babel/register": "^7.23.7",
@@ -254,7 +253,7 @@
"@storybook/react-webpack5": "8.1.11",
"@svgr/webpack": "^8.1.0",
"@testing-library/dom": "^8.20.1",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^12.8.3",
@@ -274,7 +273,6 @@
"@types/react-json-tree": "^0.6.11",
"@types/react-loadable": "^5.5.11",
"@types/react-redux": "^7.1.10",
"@types/react-resizable": "^3.0.8",
"@types/react-router-dom": "^5.3.3",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/react-table": "^7.7.20",
@@ -303,7 +301,8 @@
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0",
"enzyme": "^3.11.0",
"enzyme-matchers": "^7.1.2",
"esbuild": "^0.20.0",
"esbuild-loader": "^4.2.2",
"eslint": "^8.56.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^7.2.0",
@@ -322,7 +321,8 @@
"eslint-plugin-react-prefer-function-component": "^3.3.0",
"eslint-plugin-storybook": "^0.8.0",
"eslint-plugin-testing-library": "^6.4.0",
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
"eslint-plugin-theme-colors": "file:tools/eslint-plugin-theme-colors",
"eslint-plugin-translation-vars": "file:tools/eslint-plugin-translation-vars",
"exports-loader": "^5.0.0",
"fetch-mock": "^7.7.3",
"fork-ts-checker-webpack-plugin": "^9.0.2",
@@ -331,7 +331,9 @@
"ignore-styles": "^5.0.1",
"imports-loader": "^5.0.0",
"jest": "^29.7.0",
"jest-environment-enzyme": "^7.1.2",
"jest-environment-jsdom": "^29.7.0",
"jest-enzyme": "^7.1.2",
"jest-html-reporter": "^3.10.2",
"jest-websocket-mock": "^2.5.0",
"jsdom": "^26.0.0",
@@ -357,7 +359,6 @@
"thread-loader": "^4.0.4",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.1",
"tscw-config": "^1.1.2",
"typescript": "5.1.6",
"vm-browserify": "^1.1.2",
"webpack": "^5.97.1",
@@ -17,9 +17,9 @@
* under the License.
*/
import { useEffect, useState } from 'react';
import { Popover } from 'antd-v5';
import { Popover } from 'antd';
import type ReactAce from 'react-ace';
import type { PopoverProps } from 'antd-v5/lib/popover';
import type { PopoverProps } from 'antd/lib/popover';
import { CalculatorOutlined } from '@ant-design/icons';
import { css, styled, useTheme, t } from '@superset-ui/core';
@@ -72,7 +72,7 @@ export const SQLPopover = (props: PopoverProps & { sqlExpression: string }) => {
/>
}
placement="bottomLeft"
arrow={{ pointAtCenter: true }}
arrowPointAtCenter
title={t('SQL expression')}
{...props}
>
@@ -54,7 +54,7 @@ export const titleControls: ControlPanelSectionConfig = {
type: 'SelectControl',
freeForm: true,
clearable: true,
label: t('X Axis Title Margin'),
label: t('X AXIS TITLE BOTTOM MARGIN'),
renderTrigger: true,
default: TITLE_MARGIN_OPTIONS[0],
choices: formatSelectOptions(TITLE_MARGIN_OPTIONS),
@@ -84,12 +84,6 @@ export interface Dataset {
filter_select?: boolean;
filter_select_enabled?: boolean;
column_names?: string[];
catalog?: string;
schema?: string;
table_name?: string;
database?: Record<string, unknown>;
normalize_columns?: boolean;
always_filter_main_dttm?: boolean;
}
export interface ControlPanelState {
@@ -521,13 +515,6 @@ export enum SortSeriesType {
Avg = 'avg',
}
export type LegendPaddingType = {
top?: number;
bottom?: number;
left?: number;
right?: number;
};
export type SortSeriesData = {
sort_series_type: SortSeriesType;
sort_series_ascending: boolean;
@@ -60,14 +60,6 @@ export const D3_FORMAT_OPTIONS: [string, string][] = [
['DURATION_COL', t('Duration in ms (10500 => 0:10.5)')],
['MEMORY_DECIMAL', t('Memory in bytes - decimal (1024B => 1.024kB)')],
['MEMORY_BINARY', t('Memory in bytes - binary (1024B => 1KiB)')],
[
'MEMORY_TRANSFER_RATE_DECIMAL',
t('Memory transfer rate in bytes - decimal (1024B => 1.024kB/s)'),
],
[
'MEMORY_TRANSFER_RATE_BINARY',
t('Memory transfer rate in bytes - binary (1024B => 1KiB/s)'),
],
];
export const D3_TIME_FORMAT_DOCS = t(
@@ -18,18 +18,23 @@
*/
import NumberFormatter from '../NumberFormatter';
import { NumberFormatFunction } from '../types';
function formatMemory(
binary?: boolean,
decimals?: number,
transfer?: boolean,
): NumberFormatFunction {
return value => {
let formatted = '';
if (value === 0) {
formatted = '0B';
} else {
export default function createMemoryFormatter(
config: {
description?: string;
id?: string;
label?: string;
binary?: boolean;
decimals?: number;
} = {},
) {
const { description, id, label, binary, decimals = 2 } = config;
return new NumberFormatter({
description,
formatFunc: value => {
if (value === 0) return '0B';
const sign = value > 0 ? '' : '-';
const absValue = Math.abs(value);
@@ -42,38 +47,8 @@ function formatMemory(
suffixes.length - 1,
Math.floor(Math.log(absValue) / Math.log(base)),
);
formatted = `${sign}${parseFloat((absValue / Math.pow(base, i)).toFixed(decimals))}${suffixes[i]}`;
}
if (transfer) {
formatted = `${formatted}/s`;
}
return formatted;
};
}
export default function createMemoryFormatter(
config: {
description?: string;
id?: string;
label?: string;
binary?: boolean;
decimals?: number;
transfer?: boolean;
} = {},
) {
const {
description,
id,
label,
binary,
decimals = 2,
transfer = false,
} = config;
return new NumberFormatter({
description,
formatFunc: formatMemory(binary, decimals, transfer),
return `${sign}${parseFloat((absValue / Math.pow(base, i)).toFixed(decimals))}${suffixes[i]}`;
},
id: id ?? 'memory_format',
label: label ?? `Memory formatter`,
});
@@ -17,7 +17,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Currency, Maybe, QueryFormMetric } from '../../types';
import { Maybe, QueryFormMetric } from '../../types';
import { Column } from './Column';
export type Aggregate =
@@ -65,7 +65,7 @@ export interface Metric {
certification_details?: Maybe<string>;
certified_by?: Maybe<string>;
d3format?: Maybe<string>;
currency?: Maybe<Currency>;
currency?: Maybe<string>;
description?: Maybe<string>;
is_certified?: boolean;
verbose_name?: string;
@@ -17,9 +17,9 @@
* under the License.
*/
import { mount, shallow } from 'enzyme';
import { triggerResizeObserver } from 'resize-observer-polyfill';
import { promiseTimeout, WithLegend } from '@superset-ui/core';
import { render } from '@testing-library/react';
let renderChart = jest.fn();
let renderLegend = jest.fn();
@@ -32,18 +32,18 @@ describe.skip('WithLegend', () => {
});
it('sets className', () => {
const { container } = render(
const wrapper = shallow(
<WithLegend
className="test-class"
renderChart={renderChart}
renderLegend={renderLegend}
/>,
);
expect(container.querySelectorAll('.test-class')).toHaveLength(1);
expect(wrapper.hasClass('test-class')).toEqual(true);
});
it('renders when renderLegend is not set', () => {
const { container } = render(
const wrapper = mount(
<WithLegend
debounceTime={1}
width={500}
@@ -56,13 +56,13 @@ describe.skip('WithLegend', () => {
// Have to delay more than debounceTime (1ms)
return promiseTimeout(() => {
expect(renderChart).toHaveBeenCalledTimes(1);
expect(container.querySelectorAll('div.chart')).toHaveLength(1);
expect(container.querySelectorAll('div.legend')).toHaveLength(0);
expect(wrapper.render().find('div.chart')).toHaveLength(1);
expect(wrapper.render().find('div.legend')).toHaveLength(0);
}, 100);
});
it('renders', () => {
const { container } = render(
const wrapper = mount(
<WithLegend
debounceTime={1}
width={500}
@@ -77,13 +77,13 @@ describe.skip('WithLegend', () => {
return promiseTimeout(() => {
expect(renderChart).toHaveBeenCalledTimes(1);
expect(renderLegend).toHaveBeenCalledTimes(1);
expect(container.querySelectorAll('div.chart')).toHaveLength(1);
expect(container.querySelectorAll('div.legend')).toHaveLength(1);
expect(wrapper.render().find('div.chart')).toHaveLength(1);
expect(wrapper.render().find('div.legend')).toHaveLength(1);
}, 100);
});
it('renders without width or height', () => {
const { container } = render(
const wrapper = mount(
<WithLegend
debounceTime={1}
renderChart={renderChart}
@@ -96,13 +96,13 @@ describe.skip('WithLegend', () => {
return promiseTimeout(() => {
expect(renderChart).toHaveBeenCalledTimes(1);
expect(renderLegend).toHaveBeenCalledTimes(1);
expect(container.querySelectorAll('div.chart')).toHaveLength(1);
expect(container.querySelectorAll('div.legend')).toHaveLength(1);
expect(wrapper.render().find('div.chart')).toHaveLength(1);
expect(wrapper.render().find('div.legend')).toHaveLength(1);
}, 100);
});
it('renders legend on the left', () => {
const { container } = render(
const wrapper = mount(
<WithLegend
debounceTime={1}
position="left"
@@ -116,13 +116,13 @@ describe.skip('WithLegend', () => {
return promiseTimeout(() => {
expect(renderChart).toHaveBeenCalledTimes(1);
expect(renderLegend).toHaveBeenCalledTimes(1);
expect(container.querySelectorAll('div.chart')).toHaveLength(1);
expect(container.querySelectorAll('div.legend')).toHaveLength(1);
expect(wrapper.render().find('div.chart')).toHaveLength(1);
expect(wrapper.render().find('div.legend')).toHaveLength(1);
}, 100);
});
it('renders legend on the right', () => {
const { container } = render(
const wrapper = mount(
<WithLegend
debounceTime={1}
position="right"
@@ -136,13 +136,13 @@ describe.skip('WithLegend', () => {
return promiseTimeout(() => {
expect(renderChart).toHaveBeenCalledTimes(1);
expect(renderLegend).toHaveBeenCalledTimes(1);
expect(container.querySelectorAll('div.chart')).toHaveLength(1);
expect(container.querySelectorAll('div.legend')).toHaveLength(1);
expect(wrapper.render().find('div.chart')).toHaveLength(1);
expect(wrapper.render().find('div.legend')).toHaveLength(1);
}, 100);
});
it('renders legend on the top', () => {
const { container } = render(
const wrapper = mount(
<WithLegend
debounceTime={1}
position="top"
@@ -156,13 +156,13 @@ describe.skip('WithLegend', () => {
return promiseTimeout(() => {
expect(renderChart).toHaveBeenCalledTimes(1);
expect(renderLegend).toHaveBeenCalledTimes(1);
expect(container.querySelectorAll('div.chart')).toHaveLength(1);
expect(container.querySelectorAll('div.legend')).toHaveLength(1);
expect(wrapper.render().find('div.chart')).toHaveLength(1);
expect(wrapper.render().find('div.legend')).toHaveLength(1);
}, 100);
});
it('renders legend on the bottom', () => {
const { container } = render(
const wrapper = mount(
<WithLegend
debounceTime={1}
position="bottom"
@@ -176,13 +176,13 @@ describe.skip('WithLegend', () => {
return promiseTimeout(() => {
expect(renderChart).toHaveBeenCalledTimes(1);
expect(renderLegend).toHaveBeenCalledTimes(1);
expect(container.querySelectorAll('div.chart')).toHaveLength(1);
expect(container.querySelectorAll('div.legend')).toHaveLength(1);
expect(wrapper.render().find('div.chart')).toHaveLength(1);
expect(wrapper.render().find('div.legend')).toHaveLength(1);
}, 100);
});
it('renders legend with justifyContent set', () => {
const { container } = render(
const wrapper = mount(
<WithLegend
debounceTime={1}
position="right"
@@ -197,8 +197,8 @@ describe.skip('WithLegend', () => {
return promiseTimeout(() => {
expect(renderChart).toHaveBeenCalledTimes(1);
expect(renderLegend).toHaveBeenCalledTimes(1);
expect(container.querySelectorAll('div.chart')).toHaveLength(1);
expect(container.querySelectorAll('div.legend')).toHaveLength(1);
expect(wrapper.render().find('div.chart')).toHaveLength(1);
expect(wrapper.render().find('div.legend')).toHaveLength(1);
}, 100);
});
});
@@ -16,15 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
import '@testing-library/jest-dom';
import { render, screen, act } from '@testing-library/react';
import { ReactNode } from 'react';
import { shallow } from 'enzyme';
import ChartClient from '../../../src/chart/clients/ChartClient';
import ChartDataProvider, {
ChartDataProviderProps,
} from '../../../src/chart/components/ChartDataProvider';
import { bigNumberFormData } from '../fixtures/formData';
// Keep existing mock setup
// Note: the mock implementation of these function directly affects the expected results below
const defaultMockLoadFormData = jest.fn(({ formData }: { formData: unknown }) =>
Promise.resolve(formData),
);
@@ -49,6 +50,7 @@ const mockLoadQueryData = jest.fn<Promise<unknown>, unknown[]>(
);
const actual = jest.requireActual('../../../src/chart/clients/ChartClient');
// ChartClient is now a mock
jest.spyOn(actual, 'default').mockImplementation(() => ({
loadDatasource: mockLoadDatasource,
loadFormData: mockLoadFormData,
@@ -60,6 +62,7 @@ const ChartClientMock = ChartClient as jest.Mock<ChartClient>;
describe('ChartDataProvider', () => {
beforeEach(() => {
ChartClientMock.mockClear();
mockLoadFormData = defaultMockLoadFormData;
mockLoadFormData.mockClear();
mockLoadDatasource.mockClear();
@@ -68,17 +71,11 @@ describe('ChartDataProvider', () => {
const props: ChartDataProviderProps = {
formData: { ...bigNumberFormData },
children: ({ loading, payload, error }) => (
<div>
{loading && <span role="status">Loading...</span>}
{payload && <pre role="contentinfo">{JSON.stringify(payload)}</pre>}
{error && <div role="alert">{error.message}</div>}
</div>
),
children: () => <div />,
};
function setup(overrideProps?: Partial<ChartDataProviderProps>) {
return render(<ChartDataProvider {...props} {...overrideProps} />);
return shallow(<ChartDataProvider {...props} {...overrideProps} />);
}
it('instantiates a new ChartClient()', () => {
@@ -89,7 +86,7 @@ describe('ChartDataProvider', () => {
describe('ChartClient.loadFormData', () => {
it('calls method on mount', () => {
setup();
expect(mockLoadFormData).toHaveBeenCalledTimes(1);
expect(mockLoadFormData.mock.calls).toHaveLength(1);
expect(mockLoadFormData.mock.calls[0][0]).toEqual({
sliceId: props.sliceId,
formData: props.formData,
@@ -99,231 +96,234 @@ describe('ChartDataProvider', () => {
it('should pass formDataRequestOptions to ChartClient.loadFormData', () => {
const options = { host: 'override' };
setup({ formDataRequestOptions: options });
expect(mockLoadFormData).toHaveBeenCalledTimes(1);
expect(mockLoadFormData.mock.calls).toHaveLength(1);
expect(mockLoadFormData.mock.calls[0][1]).toEqual(options);
});
it('calls ChartClient.loadFormData when formData or sliceId change', async () => {
const { rerender } = setup();
it('calls ChartClient.loadFormData when formData or sliceId change', () => {
const wrapper = setup();
const newProps = { sliceId: 123, formData: undefined };
expect(mockLoadFormData).toHaveBeenCalledTimes(1);
expect(mockLoadFormData.mock.calls).toHaveLength(1);
rerender(<ChartDataProvider {...props} {...newProps} />);
expect(mockLoadFormData).toHaveBeenCalledTimes(2);
wrapper.setProps(newProps);
expect(mockLoadFormData.mock.calls).toHaveLength(2);
expect(mockLoadFormData.mock.calls[1][0]).toEqual(newProps);
});
});
describe('ChartClient.loadDatasource', () => {
it('does not call method if loadDatasource is false', async () => {
setup({ loadDatasource: false });
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(mockLoadDatasource).not.toHaveBeenCalled();
});
it('does not method if loadDatasource is false', () =>
new Promise(done => {
expect.assertions(1);
setup({ loadDatasource: false });
setTimeout(() => {
expect(mockLoadDatasource.mock.calls).toHaveLength(0);
done(undefined);
}, 0);
}));
it('calls method on mount if loadDatasource is true', async () => {
setup({ loadDatasource: true });
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(mockLoadDatasource).toHaveBeenCalledTimes(1);
expect(mockLoadDatasource.mock.calls[0]).toEqual([
props.formData.datasource,
undefined,
]);
});
it('calls method on mount if loadDatasource is true', () =>
new Promise(done => {
expect.assertions(2);
setup({ loadDatasource: true });
setTimeout(() => {
expect(mockLoadDatasource.mock.calls).toHaveLength(1);
expect(mockLoadDatasource.mock.calls[0][0]).toEqual(
props.formData.datasource,
);
done(undefined);
}, 0);
}));
it('should pass datasourceRequestOptions to ChartClient.loadDatasource', async () => {
const options = { host: 'override' };
setup({ loadDatasource: true, datasourceRequestOptions: options });
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(mockLoadDatasource).toHaveBeenCalledTimes(1);
expect(mockLoadDatasource.mock.calls[0][1]).toEqual(options);
});
it('should pass datasourceRequestOptions to ChartClient.loadDatasource', () =>
new Promise(done => {
expect.assertions(2);
const options = { host: 'override' };
setup({ loadDatasource: true, datasourceRequestOptions: options });
setTimeout(() => {
expect(mockLoadDatasource.mock.calls).toHaveLength(1);
expect(mockLoadDatasource.mock.calls[0][1]).toEqual(options);
done(undefined);
}, 0);
}));
it('calls ChartClient.loadDatasource if loadDatasource is true and formData or sliceId change', async () => {
const { rerender } = setup({ loadDatasource: true });
const newDatasource = 'test';
it('calls ChartClient.loadDatasource if loadDatasource is true and formData or sliceId change', () =>
new Promise(done => {
expect.assertions(3);
const newDatasource = 'test';
const wrapper = setup({ loadDatasource: true });
wrapper.setProps({
formData: { datasource: newDatasource },
sliceId: undefined,
});
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
await act(async () => {
rerender(
<ChartDataProvider
{...props}
formData={{ ...props.formData, datasource: newDatasource }}
loadDatasource
/>,
);
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(mockLoadDatasource).toHaveBeenCalledTimes(2);
expect(mockLoadDatasource.mock.calls[0]).toEqual([
props.formData.datasource,
undefined,
]);
expect(mockLoadDatasource.mock.calls[1]).toEqual([
newDatasource,
undefined,
]);
});
setTimeout(() => {
expect(mockLoadDatasource.mock.calls).toHaveLength(2);
expect(mockLoadDatasource.mock.calls[0][0]).toEqual(
props.formData.datasource,
);
expect(mockLoadDatasource.mock.calls[1][0]).toEqual(newDatasource);
done(undefined);
}, 0);
}));
});
describe('ChartClient.loadQueryData', () => {
it('calls method on mount', async () => {
setup();
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(mockLoadQueryData).toHaveBeenCalledTimes(1);
expect(mockLoadQueryData.mock.calls[0]).toEqual([
props.formData,
undefined,
]);
});
it('calls method on mount', () =>
new Promise(done => {
expect.assertions(2);
setup();
setTimeout(() => {
expect(mockLoadQueryData.mock.calls).toHaveLength(1);
expect(mockLoadQueryData.mock.calls[0][0]).toEqual(props.formData);
done(undefined);
}, 0);
}));
it('should pass queryDataRequestOptions to ChartClient.loadQueryData', async () => {
const options = { host: 'override' };
setup({ queryRequestOptions: options });
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(mockLoadQueryData).toHaveBeenCalledTimes(1);
expect(mockLoadQueryData).toHaveBeenCalledWith(
expect.anything(),
options,
);
});
it('should pass queryDataRequestOptions to ChartClient.loadQueryData', () =>
new Promise(done => {
expect.assertions(2);
const options = { host: 'override' };
setup({ queryRequestOptions: options });
setTimeout(() => {
expect(mockLoadQueryData.mock.calls).toHaveLength(1);
expect(mockLoadQueryData.mock.calls[0][1]).toEqual(options);
done(undefined);
}, 0);
}));
it('calls ChartClient.loadQueryData when formData or sliceId change', async () => {
const { rerender } = setup();
const newFormData = { key: 'test' };
it('calls ChartClient.loadQueryData when formData or sliceId change', () =>
new Promise(done => {
expect.assertions(3);
const newFormData = { key: 'test' };
const wrapper = setup();
wrapper.setProps({ formData: newFormData, sliceId: undefined });
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
await act(async () => {
rerender(<ChartDataProvider {...props} formData={newFormData} />);
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(mockLoadQueryData).toHaveBeenCalledTimes(2);
expect(mockLoadQueryData.mock.calls[0]).toEqual([
props.formData,
undefined,
]);
expect(mockLoadQueryData.mock.calls[1]).toEqual([newFormData, undefined]);
});
setTimeout(() => {
expect(mockLoadQueryData.mock.calls).toHaveLength(2);
expect(mockLoadQueryData.mock.calls[0][0]).toEqual(props.formData);
expect(mockLoadQueryData.mock.calls[1][0]).toEqual(newFormData);
done(undefined);
}, 0);
}));
});
describe('children', () => {
it('shows loading state initially', async () => {
mockLoadFormData.mockImplementation(() => new Promise(() => {}));
mockLoadQueryData.mockImplementation(() => new Promise(() => {}));
mockLoadDatasource.mockImplementation(() => new Promise(() => {}));
it('calls children({ loading: true }) when loading', () => {
const children = jest.fn<ReactNode, unknown[]>();
setup({ children });
setup();
await screen.findByRole('status');
// during the first tick (before more promises resolve) loading is true
expect(children.mock.calls).toHaveLength(1);
expect(children.mock.calls[0][0]).toEqual({ loading: true });
});
it('shows payload when loaded', async () => {
mockLoadFormData.mockResolvedValue(props.formData);
mockLoadQueryData.mockResolvedValue([props.formData]);
mockLoadDatasource.mockResolvedValue(props.formData.datasource);
it('calls children({ payload }) when loaded', () =>
new Promise(done => {
expect.assertions(2);
const children = jest.fn<ReactNode, unknown[]>();
setup({ children, loadDatasource: true });
setup({ loadDatasource: true });
setTimeout(() => {
expect(children.mock.calls).toHaveLength(2);
expect(children.mock.calls[1][0]).toEqual({
payload: {
formData: props.formData,
datasource: props.formData.datasource,
queriesData: [props.formData],
},
});
done(undefined);
}, 0);
}));
const payloadElement = await screen.findByRole('contentinfo');
const actualPayload = JSON.parse(payloadElement.textContent || '');
it('calls children({ error }) upon request error', () =>
new Promise(done => {
expect.assertions(2);
const children = jest.fn<ReactNode, unknown[]>();
mockLoadFormData = jest.fn(() => Promise.reject(new Error('error')));
expect(actualPayload).toEqual({
formData: props.formData,
datasource: props.formData.datasource,
queriesData: [props.formData],
});
});
setup({ children });
it('shows error message upon request error', async () => {
const errorMessage = 'error';
mockLoadFormData.mockRejectedValue(new Error(errorMessage));
setTimeout(() => {
expect(children.mock.calls).toHaveLength(2); // loading + error
expect(children.mock.calls[1][0]).toEqual({
error: new Error('error'),
});
done(undefined);
}, 0);
}));
setup();
it('calls children({ error }) upon JS error', () =>
new Promise(done => {
expect.assertions(2);
const children = jest.fn<ReactNode, unknown[]>();
const errorElement = await screen.findByRole('alert');
expect(errorElement).toHaveAttribute('role', 'alert');
expect(errorElement).toHaveTextContent(errorMessage);
});
mockLoadFormData = jest.fn(() => {
throw new Error('non-async error');
});
it('shows error message upon JS error', async () => {
mockLoadFormData.mockImplementation(() => {
throw new Error('non-async error');
});
setup({ children });
setup();
const errorElement = await screen.findByRole('alert');
expect(errorElement).toHaveAttribute('role', 'alert');
expect(errorElement).toHaveTextContent('non-async error');
});
setTimeout(() => {
expect(children.mock.calls).toHaveLength(2); // loading + error
expect(children.mock.calls[1][0]).toEqual({
error: new Error('non-async error'),
});
done(undefined);
}, 0);
}));
});
describe('callbacks', () => {
it('calls onLoaded when loaded', async () => {
const onLoaded = jest.fn();
mockLoadFormData.mockResolvedValue(props.formData);
mockLoadQueryData.mockResolvedValue([props.formData]);
mockLoadDatasource.mockResolvedValue(props.formData.datasource);
it('calls onLoad(payload) when loaded', () =>
new Promise(done => {
expect.assertions(2);
const onLoaded = jest.fn<void, unknown[]>();
setup({ onLoaded, loadDatasource: true });
setup({ onLoaded, loadDatasource: true });
setTimeout(() => {
expect(onLoaded.mock.calls).toHaveLength(1);
expect(onLoaded.mock.calls[0][0]).toEqual({
formData: props.formData,
datasource: props.formData.datasource,
queriesData: [props.formData],
});
done(undefined);
}, 0);
}));
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
it('calls onError(error) upon request error', () =>
new Promise(done => {
expect.assertions(2);
const onError = jest.fn<void, unknown[]>();
mockLoadFormData = jest.fn(() => Promise.reject(new Error('error')));
expect(onLoaded).toHaveBeenCalledTimes(1);
expect(onLoaded).toHaveBeenCalledWith({
formData: props.formData,
datasource: props.formData.datasource,
queriesData: [props.formData],
});
});
setup({ onError });
setTimeout(() => {
expect(onError.mock.calls).toHaveLength(1);
expect(onError.mock.calls[0][0]).toEqual(new Error('error'));
done(undefined);
}, 0);
}));
it('calls onError upon request error', async () => {
const onError = jest.fn();
mockLoadFormData.mockRejectedValue(new Error('error'));
it('calls onError(error) upon JS error', () =>
new Promise(done => {
expect.assertions(2);
const onError = jest.fn<void, unknown[]>();
setup({ onError });
mockLoadFormData = jest.fn(() => {
throw new Error('non-async error');
});
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(onError).toHaveBeenCalledTimes(1);
expect(onError).toHaveBeenCalledWith(new Error('error'));
});
it('calls onError upon JS error', async () => {
const onError = jest.fn();
mockLoadFormData.mockImplementation(() => {
throw new Error('non-async error');
});
setup({ onError });
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(onError).toHaveBeenCalledTimes(1);
expect(onError).toHaveBeenCalledWith(new Error('non-async error'));
});
setup({ onError });
setTimeout(() => {
expect(onError.mock.calls).toHaveLength(1);
expect(onError.mock.calls[0][0]).toEqual(
new Error('non-async error'),
);
done(undefined);
}, 0);
}));
});
});
@@ -17,12 +17,10 @@
* under the License.
*/
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import { ReactElement } from 'react';
import mockConsole, { RestoreConsole } from 'jest-mock-console';
import { triggerResizeObserver } from 'resize-observer-polyfill';
import { ErrorBoundary } from 'react-error-boundary';
import ErrorBoundary from 'react-error-boundary';
import {
promiseTimeout,
@@ -30,7 +28,9 @@ import {
supersetTheme,
ThemeProvider,
} from '@superset-ui/core';
import { mount as enzymeMount } from 'enzyme';
import { WrapperProps } from '../../../src/chart/components/SuperChart';
import NoResultsComponent from '../../../src/chart/components/NoResultsComponent';
import {
ChartKeys,
@@ -44,39 +44,45 @@ const DEFAULT_QUERIES_DATA = [
{ data: ['foo2', 'bar2'] },
];
// Fix for expect outside test block - move expectDimension into a test utility
// Replace expectDimension function with a non-expect version
function getDimensionText(container: HTMLElement) {
const dimensionEl = container.querySelector('.dimension');
return dimensionEl?.textContent || '';
function expectDimension(
renderedWrapper: cheerio.Cheerio,
width: number,
height: number,
) {
expect(renderedWrapper.find('.dimension').text()).toEqual(
[width, height].join('x'),
);
}
const renderWithTheme = (component: ReactElement) =>
render(component, {
wrapper: ({ children }) => (
<ThemeProvider theme={supersetTheme}>{children}</ThemeProvider>
),
const mount = (component: ReactElement) =>
enzymeMount(component, {
wrappingComponent: ThemeProvider,
wrappingComponentProps: { theme: supersetTheme },
});
describe('SuperChart', () => {
jest.setTimeout(5000);
let restoreConsole: RestoreConsole;
// TODO: rewrite to rtl
describe.skip('SuperChart', () => {
const plugins = [
new DiligentChartPlugin().configure({ key: ChartKeys.DILIGENT }),
new BuggyChartPlugin().configure({ key: ChartKeys.BUGGY }),
];
let restoreConsole: RestoreConsole;
beforeAll(() => {
plugins.forEach(p => {
p.unregister().register();
});
});
afterAll(() => {
plugins.forEach(p => {
p.unregister();
});
});
beforeEach(() => {
restoreConsole = mockConsole();
triggerResizeObserver([]); // Reset any pending resize observers
});
afterEach(() => {
@@ -99,16 +105,14 @@ describe('SuperChart', () => {
afterEach(() => {
window.removeEventListener('error', onError);
});
it('should have correct number of errors', () => {
// eslint-disable-next-line jest/no-standalone-expect
expect(actualErrors).toBe(expectedErrors);
expectedErrors = 0;
});
it('renders default FallbackComponent', async () => {
expectedErrors = 1;
renderWithTheme(
const wrapper = mount(
<SuperChart
chartType={ChartKeys.BUGGY}
queriesData={[DEFAULT_QUERY_DATA]}
@@ -116,19 +120,16 @@ describe('SuperChart', () => {
height="200"
/>,
);
expect(
await screen.findByText('Oops! An error occurred!'),
).toBeInTheDocument();
await new Promise(resolve => setImmediate(resolve));
wrapper.update();
expect(wrapper.text()).toContain('Oops! An error occurred!');
});
it('renders custom FallbackComponent', async () => {
it('renders custom FallbackComponent', () => {
expectedErrors = 1;
const CustomFallbackComponent = jest.fn(() => (
<div>Custom Fallback!</div>
));
renderWithTheme(
const wrapper = mount(
<SuperChart
chartType={ChartKeys.BUGGY}
queriesData={[DEFAULT_QUERY_DATA]}
@@ -138,13 +139,15 @@ describe('SuperChart', () => {
/>,
);
expect(await screen.findByText('Custom Fallback!')).toBeInTheDocument();
expect(CustomFallbackComponent).toHaveBeenCalledTimes(1);
return promiseTimeout(() => {
expect(wrapper.render().find('div.test-component')).toHaveLength(0);
expect(CustomFallbackComponent).toHaveBeenCalledTimes(1);
});
});
it('call onErrorBoundary', async () => {
it('call onErrorBoundary', () => {
expectedErrors = 1;
const handleError = jest.fn();
renderWithTheme(
mount(
<SuperChart
chartType={ChartKeys.BUGGY}
queriesData={[DEFAULT_QUERY_DATA]}
@@ -154,20 +157,17 @@ describe('SuperChart', () => {
/>,
);
await screen.findByText('Oops! An error occurred!');
expect(handleError).toHaveBeenCalledTimes(1);
return promiseTimeout(() => {
expect(handleError).toHaveBeenCalledTimes(1);
});
});
// Update the test cases
it('does not include ErrorBoundary if told so', async () => {
it('does not include ErrorBoundary if told so', () => {
expectedErrors = 1;
const inactiveErrorHandler = jest.fn();
const activeErrorHandler = jest.fn();
renderWithTheme(
<ErrorBoundary
fallbackRender={() => <div>Error!</div>}
onError={activeErrorHandler}
>
mount(
// @ts-ignore
<ErrorBoundary onError={activeErrorHandler}>
<SuperChart
disableErrorBoundary
chartType={ChartKeys.BUGGY}
@@ -179,23 +179,15 @@ describe('SuperChart', () => {
</ErrorBoundary>,
);
await screen.findByText('Error!');
expect(activeErrorHandler).toHaveBeenCalledTimes(1);
expect(inactiveErrorHandler).not.toHaveBeenCalled();
return promiseTimeout(() => {
expect(activeErrorHandler).toHaveBeenCalledTimes(1);
expect(inactiveErrorHandler).toHaveBeenCalledTimes(0);
});
});
});
// Helper function to find elements by class name
const findByClassName = (container: HTMLElement, className: string) =>
container.querySelector(`.${className}`);
// Update test cases
// Update timeout for all async tests
jest.setTimeout(10000);
// Update the props test to wait for component to render
it('passes the props to renderer correctly', async () => {
const { container } = renderWithTheme(
it('passes the props to renderer correctly', () => {
const wrapper = mount(
<SuperChart
chartType={ChartKeys.DILIGENT}
queriesData={[DEFAULT_QUERY_DATA]}
@@ -205,123 +197,15 @@ describe('SuperChart', () => {
/>,
);
await promiseTimeout(() => {
const testComponent = findByClassName(container, 'test-component');
expect(testComponent).not.toBeNull();
expect(testComponent).toBeInTheDocument();
expect(getDimensionText(container)).toBe('101x118');
return promiseTimeout(() => {
const renderedWrapper = wrapper.render();
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
expectDimension(renderedWrapper, 101, 118);
});
});
// Helper function to create a sized wrapper
const createSizedWrapper = () => {
const wrapper = document.createElement('div');
wrapper.style.width = '300px';
wrapper.style.height = '300px';
wrapper.style.position = 'relative';
wrapper.style.display = 'block';
return wrapper;
};
// Update dimension tests to wait for resize observer
// First, increase the timeout for all tests
jest.setTimeout(20000);
// Update the waitForDimensions helper to include a retry mechanism
// Update waitForDimensions to avoid await in loop
const waitForDimensions = async (
container: HTMLElement,
expectedWidth: number,
expectedHeight: number,
) => {
const maxAttempts = 5;
const interval = 100;
return new Promise<void>((resolve, reject) => {
let attempts = 0;
const checkDimension = () => {
const testComponent = container.querySelector('.test-component');
const dimensionEl = container.querySelector('.dimension');
if (!testComponent || !dimensionEl) {
if (attempts >= maxAttempts) {
reject(new Error('Elements not found'));
return;
}
attempts += 1;
setTimeout(checkDimension, interval);
return;
}
if (dimensionEl.textContent !== `${expectedWidth}x${expectedHeight}`) {
if (attempts >= maxAttempts) {
reject(new Error('Dimension mismatch'));
return;
}
attempts += 1;
setTimeout(checkDimension, interval);
return;
}
resolve();
};
checkDimension();
});
};
// Update the resize observer trigger to ensure it's called after component mount
it.skip('works when width and height are percent', async () => {
const { container } = renderWithTheme(
<SuperChart
chartType={ChartKeys.DILIGENT}
queriesData={[DEFAULT_QUERY_DATA]}
debounceTime={1}
width="100%"
height="100%"
/>,
);
// Wait for initial render
await new Promise(resolve => setTimeout(resolve, 50));
triggerResizeObserver([
{
contentRect: {
width: 300,
height: 300,
top: 0,
left: 0,
right: 300,
bottom: 300,
x: 0,
y: 0,
toJSON() {
return {
width: this.width,
height: this.height,
top: this.top,
left: this.left,
right: this.right,
bottom: this.bottom,
x: this.x,
y: this.y,
};
},
},
borderBoxSize: [{ blockSize: 300, inlineSize: 300 }],
contentBoxSize: [{ blockSize: 300, inlineSize: 300 }],
devicePixelContentBoxSize: [{ blockSize: 300, inlineSize: 300 }],
target: document.createElement('div'),
},
]);
await waitForDimensions(container, 300, 300);
});
it('passes the props with multiple queries to renderer correctly', async () => {
const { container } = renderWithTheme(
it('passes the props with multiple queries to renderer correctly', () => {
const wrapper = mount(
<SuperChart
chartType={ChartKeys.DILIGENT}
queriesData={DEFAULT_QUERIES_DATA}
@@ -331,25 +215,42 @@ describe('SuperChart', () => {
/>,
);
await promiseTimeout(() => {
const testComponent = container.querySelector('.test-component');
expect(testComponent).not.toBeNull();
expect(testComponent).toBeInTheDocument();
expect(getDimensionText(container)).toBe('101x118');
return promiseTimeout(() => {
const renderedWrapper = wrapper.render();
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
expectDimension(renderedWrapper, 101, 118);
});
});
it('passes the props with multiple queries and single query to renderer correctly (backward compatibility)', () => {
const wrapper = mount(
<SuperChart
chartType={ChartKeys.DILIGENT}
queriesData={DEFAULT_QUERIES_DATA}
width={101}
height={118}
formData={{ abc: 1 }}
/>,
);
return promiseTimeout(() => {
const renderedWrapper = wrapper.render();
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
expectDimension(renderedWrapper, 101, 118);
});
});
describe('supports NoResultsComponent', () => {
it('renders NoResultsComponent when queriesData is missing', () => {
renderWithTheme(
const wrapper = mount(
<SuperChart chartType={ChartKeys.DILIGENT} width="200" height="200" />,
);
expect(screen.getByText('No Results')).toBeInTheDocument();
expect(wrapper.find(NoResultsComponent)).toHaveLength(1);
});
it('renders NoResultsComponent when queriesData data is null', () => {
renderWithTheme(
const wrapper = mount(
<SuperChart
chartType={ChartKeys.DILIGENT}
queriesData={[{ data: null }]}
@@ -358,12 +259,116 @@ describe('SuperChart', () => {
/>,
);
expect(screen.getByText('No Results')).toBeInTheDocument();
expect(wrapper.find(NoResultsComponent)).toHaveLength(1);
});
});
describe('supports dynamic width and/or height', () => {
// Add MyWrapper component definition
it('works with width and height that are numbers', () => {
const wrapper = mount(
<SuperChart
chartType={ChartKeys.DILIGENT}
queriesData={[DEFAULT_QUERY_DATA]}
width={100}
height={100}
/>,
);
return promiseTimeout(() => {
const renderedWrapper = wrapper.render();
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
expectDimension(renderedWrapper, 100, 100);
});
});
it('works when width and height are percent', () => {
const wrapper = mount(
<SuperChart
chartType={ChartKeys.DILIGENT}
queriesData={[DEFAULT_QUERY_DATA]}
debounceTime={1}
width="100%"
height="100%"
/>,
);
triggerResizeObserver();
return promiseTimeout(() => {
const renderedWrapper = wrapper.render();
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
expectDimension(renderedWrapper, 300, 300);
}, 100);
});
it('works when only width is percent', () => {
const wrapper = mount(
<SuperChart
chartType={ChartKeys.DILIGENT}
queriesData={[DEFAULT_QUERY_DATA]}
debounceTime={1}
width="50%"
height="125"
/>,
);
// @ts-ignore
triggerResizeObserver([{ contentRect: { height: 125, width: 150 } }]);
return promiseTimeout(() => {
const renderedWrapper = wrapper.render();
const boundingBox = renderedWrapper
.find('div.test-component')
.parent()
.parent()
.parent();
expect(boundingBox.css('width')).toEqual('50%');
expect(boundingBox.css('height')).toEqual('125px');
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
expectDimension(renderedWrapper, 150, 125);
}, 100);
});
it('works when only height is percent', () => {
const wrapper = mount(
<SuperChart
chartType={ChartKeys.DILIGENT}
queriesData={[DEFAULT_QUERY_DATA]}
debounceTime={1}
width="50"
height="25%"
/>,
);
// @ts-ignore
triggerResizeObserver([{ contentRect: { height: 75, width: 50 } }]);
return promiseTimeout(() => {
const renderedWrapper = wrapper.render();
const boundingBox = renderedWrapper
.find('div.test-component')
.parent()
.parent()
.parent();
expect(boundingBox.css('width')).toEqual('50px');
expect(boundingBox.css('height')).toEqual('25%');
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
expectDimension(renderedWrapper, 50, 75);
}, 100);
});
it('works when width and height are not specified', () => {
const wrapper = mount(
<SuperChart
chartType={ChartKeys.DILIGENT}
queriesData={[DEFAULT_QUERY_DATA]}
debounceTime={1}
/>,
);
triggerResizeObserver();
return promiseTimeout(() => {
const renderedWrapper = wrapper.render();
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
expectDimension(renderedWrapper, 300, 400);
}, 100);
});
});
describe('supports Wrapper', () => {
function MyWrapper({ width, height, children }: WrapperProps) {
return (
<div>
@@ -375,81 +380,50 @@ describe('SuperChart', () => {
);
}
it('works with width and height that are numbers', async () => {
const { container } = renderWithTheme(
it('works with width and height that are numbers', () => {
const wrapper = mount(
<SuperChart
chartType={ChartKeys.DILIGENT}
queriesData={[DEFAULT_QUERY_DATA]}
width={100}
height={100}
Wrapper={MyWrapper}
/>,
);
await promiseTimeout(() => {
const testComponent = container.querySelector('.test-component');
expect(testComponent).not.toBeNull();
expect(testComponent).toBeInTheDocument();
expect(getDimensionText(container)).toBe('100x100');
});
return promiseTimeout(() => {
const renderedWrapper = wrapper.render();
expect(renderedWrapper.find('div.wrapper-insert')).toHaveLength(1);
expect(renderedWrapper.find('div.wrapper-insert').text()).toEqual(
'100x100',
);
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
expectDimension(renderedWrapper, 100, 100);
}, 100);
});
it.skip('works when width and height are percent', async () => {
const wrapper = createSizedWrapper();
document.body.appendChild(wrapper);
const { container } = renderWithTheme(
<div style={{ width: '100%', height: '100%', position: 'absolute' }}>
<SuperChart
chartType={ChartKeys.DILIGENT}
queriesData={[DEFAULT_QUERY_DATA]}
debounceTime={1}
width="100%"
height="100%"
Wrapper={MyWrapper}
/>
</div>,
it('works when width and height are percent', () => {
const wrapper = mount(
<SuperChart
chartType={ChartKeys.DILIGENT}
queriesData={[DEFAULT_QUERY_DATA]}
debounceTime={1}
width="100%"
height="100%"
Wrapper={MyWrapper}
/>,
);
triggerResizeObserver();
wrapper.appendChild(container);
// Wait for initial render
await new Promise(resolve => setTimeout(resolve, 100));
// Trigger resize
triggerResizeObserver([
{
contentRect: {
width: 300,
height: 300,
top: 0,
left: 0,
right: 300,
bottom: 300,
x: 0,
y: 0,
toJSON() {
return this;
},
},
borderBoxSize: [{ blockSize: 300, inlineSize: 300 }],
contentBoxSize: [{ blockSize: 300, inlineSize: 300 }],
devicePixelContentBoxSize: [{ blockSize: 300, inlineSize: 300 }],
target: wrapper,
},
]);
// Wait for resize to be processed
await new Promise(resolve => setTimeout(resolve, 200));
// Check dimensions
const wrapperInsert = container.querySelector('.wrapper-insert');
expect(wrapperInsert).not.toBeNull();
expect(wrapperInsert).toBeInTheDocument();
expect(wrapperInsert).toHaveTextContent('300x300');
await waitForDimensions(container, 300, 300);
document.body.removeChild(wrapper);
}, 30000);
return promiseTimeout(() => {
const renderedWrapper = wrapper.render();
expect(renderedWrapper.find('div.wrapper-insert')).toHaveLength(1);
expect(renderedWrapper.find('div.wrapper-insert').text()).toEqual(
'300x300',
);
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
expectDimension(renderedWrapper, 300, 300);
}, 100);
});
});
});
@@ -17,11 +17,16 @@
* under the License.
*/
import '@testing-library/jest-dom';
import { ReactElement } from 'react';
import { ReactElement, ReactNode } from 'react';
import { mount } from 'enzyme';
import mockConsole, { RestoreConsole } from 'jest-mock-console';
import { ChartProps, supersetTheme, ThemeProvider } from '@superset-ui/core';
import { render, screen, waitFor } from '@testing-library/react';
import {
ChartProps,
promiseTimeout,
supersetTheme,
SupersetTheme,
ThemeProvider,
} from '@superset-ui/core';
import SuperChartCore from '../../../src/chart/components/SuperChartCore';
import {
ChartKeys,
@@ -30,11 +35,25 @@ import {
SlowChartPlugin,
} from './MockChartPlugins';
const renderWithTheme = (component: ReactElement) =>
render(<ThemeProvider theme={supersetTheme}>{component}</ThemeProvider>);
const Wrapper = ({
theme,
children,
}: {
theme: SupersetTheme;
children: ReactNode;
}) => <ThemeProvider theme={theme}>{children}</ThemeProvider>;
const styledMount = (component: ReactElement) =>
mount(component, {
wrappingComponent: Wrapper,
wrappingComponentProps: {
theme: supersetTheme,
},
});
describe('SuperChartCore', () => {
const chartProps = new ChartProps();
const plugins = [
new DiligentChartPlugin().configure({ key: ChartKeys.DILIGENT }),
new LazyChartPlugin().configure({ key: ChartKeys.LAZY }),
@@ -44,7 +63,6 @@ describe('SuperChartCore', () => {
let restoreConsole: RestoreConsole;
beforeAll(() => {
jest.setTimeout(30000);
plugins.forEach(p => {
p.unregister().register();
});
@@ -65,83 +83,72 @@ describe('SuperChartCore', () => {
});
describe('registered charts', () => {
it('renders registered chart', async () => {
const { container } = renderWithTheme(
it('renders registered chart', () => {
const wrapper = styledMount(
<SuperChartCore
chartType={ChartKeys.DILIGENT}
chartProps={chartProps}
/>,
);
await waitFor(() => {
expect(container.querySelector('.test-component')).toBeInTheDocument();
return promiseTimeout(() => {
expect(wrapper.render().find('div.test-component')).toHaveLength(1);
});
});
it('renders registered chart with lazy loading', async () => {
const { container } = renderWithTheme(
it('renders registered chart with lazy loading', () => {
const wrapper = styledMount(
<SuperChartCore chartType={ChartKeys.LAZY} />,
);
await waitFor(() => {
expect(container.querySelector('.test-component')).toBeInTheDocument();
return promiseTimeout(() => {
expect(wrapper.render().find('div.test-component')).toHaveLength(1);
});
});
it('does not render if chartType is not set', async () => {
it('does not render if chartType is not set', () => {
// Suppress warning
// @ts-ignore chartType is required
const { container } = renderWithTheme(<SuperChartCore />);
const wrapper = styledMount(<SuperChartCore />);
await waitFor(() => {
const testComponent = container.querySelector('.test-component');
expect(testComponent).not.toBeInTheDocument();
});
return promiseTimeout(() => {
expect(wrapper.render().children()).toHaveLength(0);
}, 5);
});
it('adds id to container if specified', async () => {
const { container } = renderWithTheme(
it('adds id to container if specified', () => {
const wrapper = styledMount(
<SuperChartCore chartType={ChartKeys.DILIGENT} id="the-chart" />,
);
await waitFor(() => {
const element = container.querySelector('#the-chart');
expect(element).toBeInTheDocument();
expect(element).toHaveAttribute('id', 'the-chart');
return promiseTimeout(() => {
expect(wrapper.render().attr('id')).toEqual('the-chart');
});
});
it('adds class to container if specified', async () => {
const { container } = renderWithTheme(
it('adds class to container if specified', () => {
const wrapper = styledMount(
<SuperChartCore chartType={ChartKeys.DILIGENT} className="the-chart" />,
);
await waitFor(() => {
const element = container.querySelector('.the-chart');
expect(element).toBeInTheDocument();
expect(element).toHaveClass('the-chart');
});
return promiseTimeout(() => {
expect(wrapper.hasClass('the-chart')).toBeTruthy();
}, 0);
});
it('uses overrideTransformProps when specified', async () => {
renderWithTheme(
it('uses overrideTransformProps when specified', () => {
const wrapper = styledMount(
<SuperChartCore
chartType={ChartKeys.DILIGENT}
overrideTransformProps={() => ({ message: 'hulk' })}
/>,
);
await waitFor(() => {
expect(screen.getByText('hulk')).toBeInTheDocument();
return promiseTimeout(() => {
expect(wrapper.render().find('.message').text()).toEqual('hulk');
});
});
it('uses preTransformProps when specified', async () => {
it('uses preTransformProps when specified', () => {
const chartPropsWithPayload = new ChartProps({
queriesData: [{ message: 'hulk' }],
theme: supersetTheme,
});
renderWithTheme(
const wrapper = styledMount(
<SuperChartCore
chartType={ChartKeys.DILIGENT}
preTransformProps={() => chartPropsWithPayload}
@@ -149,77 +156,69 @@ describe('SuperChartCore', () => {
/>,
);
await waitFor(() => {
expect(screen.getByText('hulk')).toBeInTheDocument();
return promiseTimeout(() => {
expect(wrapper.render().find('.message').text()).toEqual('hulk');
});
});
it('uses postTransformProps when specified', async () => {
renderWithTheme(
it('uses postTransformProps when specified', () => {
const wrapper = styledMount(
<SuperChartCore
chartType={ChartKeys.DILIGENT}
postTransformProps={() => ({ message: 'hulk' })}
/>,
);
await waitFor(() => {
expect(screen.getByText('hulk')).toBeInTheDocument();
return promiseTimeout(() => {
expect(wrapper.render().find('.message').text()).toEqual('hulk');
});
});
it('renders if chartProps is not specified', async () => {
const { container } = renderWithTheme(
it('renders if chartProps is not specified', () => {
const wrapper = styledMount(
<SuperChartCore chartType={ChartKeys.DILIGENT} />,
);
await waitFor(() => {
expect(container.querySelector('.test-component')).toBeInTheDocument();
return promiseTimeout(() => {
expect(wrapper.render().find('div.test-component')).toHaveLength(1);
});
});
it('does not render anything while waiting for Chart code to load', () => {
const { container } = renderWithTheme(
const wrapper = styledMount(
<SuperChartCore chartType={ChartKeys.SLOW} />,
);
const testComponent = container.querySelector('.test-component');
expect(testComponent).not.toBeInTheDocument();
return promiseTimeout(() => {
expect(wrapper.render().children()).toHaveLength(0);
});
});
it('eventually renders after Chart is loaded', async () => {
const { container } = renderWithTheme(
it('eventually renders after Chart is loaded', () => {
// Suppress warning
const wrapper = styledMount(
<SuperChartCore chartType={ChartKeys.SLOW} />,
);
await waitFor(
() => {
expect(
container.querySelector('.test-component'),
).toBeInTheDocument();
},
{ timeout: 2000 },
);
return promiseTimeout(() => {
expect(wrapper.render().find('div.test-component')).toHaveLength(1);
}, 1500);
});
it('does not render if chartProps is null', async () => {
const { container } = renderWithTheme(
it('does not render if chartProps is null', () => {
const wrapper = styledMount(
<SuperChartCore chartType={ChartKeys.DILIGENT} chartProps={null} />,
);
await waitFor(() => {
expect(container).toBeEmptyDOMElement();
return promiseTimeout(() => {
expect(wrapper.render().find('div.test-component')).toHaveLength(0);
});
});
});
describe('unregistered charts', () => {
it('renders error message', async () => {
renderWithTheme(
it('renders error message', () => {
const wrapper = styledMount(
<SuperChartCore chartType="4d-pie-chart" chartProps={chartProps} />,
);
await waitFor(() => {
expect(screen.getByRole('alert')).toBeInTheDocument();
return promiseTimeout(() => {
expect(wrapper.render().find('.alert')).toHaveLength(1);
});
});
});
@@ -17,11 +17,10 @@
* under the License.
*/
import '@testing-library/jest-dom';
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { mount } from 'enzyme';
import { reactify } from '@superset-ui/core';
import { render, screen } from '@testing-library/react';
import { RenderFuncType } from '../../../src/chart/components/reactify';
describe('reactify(renderFn)', () => {
@@ -79,18 +78,14 @@ describe('reactify(renderFn)', () => {
it('returns a React component class', () =>
new Promise(done => {
render(<TestComponent />);
const wrapper = mount(<TestComponent />);
expect(renderFn).toHaveBeenCalledTimes(1);
expect(screen.getByText('abc')).toBeInTheDocument();
expect(screen.getByText('abc').parentNode).toHaveAttribute('id', 'test');
expect(wrapper.html()).toEqual('<div id="test"><b>abc</b></div>');
setTimeout(() => {
expect(renderFn).toHaveBeenCalledTimes(2);
expect(screen.getByText('def')).toBeInTheDocument();
expect(screen.getByText('def').parentNode).toHaveAttribute(
'id',
'test',
);
expect(wrapper.html()).toEqual('<div id="test"><b>def</b></div>');
wrapper.unmount();
done(undefined);
}, 20);
}));
@@ -124,9 +119,8 @@ describe('reactify(renderFn)', () => {
describe('defaultProps', () => {
it('has defaultProps if renderFn.defaultProps is defined', () => {
expect(TheChart.defaultProps).toBe(renderFn.defaultProps);
render(<TheChart id="test" />);
expect(screen.getByText('ghi')).toBeInTheDocument();
expect(screen.getByText('ghi').parentNode).toHaveAttribute('id', 'test');
const wrapper = mount(<TheChart id="test" />);
expect(wrapper.html()).toEqual('<div id="test"><b>ghi</b></div>');
});
it('does not have defaultProps if renderFn.defaultProps is not defined', () => {
const AnotherChart = reactify(() => {});
@@ -142,9 +136,9 @@ describe('reactify(renderFn)', () => {
});
it('calls willUnmount hook when it is provided', () =>
new Promise(done => {
const { unmount } = render(<AnotherTestComponent />);
const wrapper = mount(<AnotherTestComponent />);
setTimeout(() => {
unmount();
wrapper.unmount();
expect(willUnmountCb).toHaveBeenCalledTimes(1);
done(undefined);
}, 20);
@@ -83,54 +83,6 @@ test('formats bytes in human readable format with additional binary option', ()
expect(formatter(Math.pow(1024, 10))).toBe('1048576YiB');
});
test('formats bytes in human readable format with additional transfer option', () => {
const formatter = createMemoryFormatter({ transfer: true });
expect(formatter(0)).toBe('0B/s');
expect(formatter(50)).toBe('50B/s');
expect(formatter(555)).toBe('555B/s');
expect(formatter(1000)).toBe('1kB/s');
expect(formatter(1111)).toBe('1.11kB/s');
expect(formatter(1024)).toBe('1.02kB/s');
expect(formatter(1337)).toBe('1.34kB/s');
expect(formatter(1999)).toBe('2kB/s');
expect(formatter(10 * 1000)).toBe('10kB/s');
expect(formatter(100 * 1000)).toBe('100kB/s');
expect(formatter(Math.pow(1000, 2))).toBe('1MB/s');
expect(formatter(Math.pow(1000, 3))).toBe('1GB/s');
expect(formatter(Math.pow(1000, 4))).toBe('1TB/s');
expect(formatter(Math.pow(1000, 5))).toBe('1PB/s');
expect(formatter(Math.pow(1000, 6))).toBe('1EB/s');
expect(formatter(Math.pow(1000, 7))).toBe('1ZB/s');
expect(formatter(Math.pow(1000, 8))).toBe('1YB/s');
expect(formatter(Math.pow(1000, 9))).toBe('1RB/s');
expect(formatter(Math.pow(1000, 10))).toBe('1QB/s');
expect(formatter(Math.pow(1000, 11))).toBe('1000QB/s');
expect(formatter(Math.pow(1000, 12))).toBe('1000000QB/s');
});
test('formats bytes in human readable format with additional binary AND transfer option', () => {
const formatter = createMemoryFormatter({ binary: true, transfer: true });
expect(formatter(0)).toBe('0B/s');
expect(formatter(50)).toBe('50B/s');
expect(formatter(555)).toBe('555B/s');
expect(formatter(1000)).toBe('1000B/s');
expect(formatter(1111)).toBe('1.08KiB/s');
expect(formatter(1024)).toBe('1KiB/s');
expect(formatter(1337)).toBe('1.31KiB/s');
expect(formatter(2047)).toBe('2KiB/s');
expect(formatter(10 * 1024)).toBe('10KiB/s');
expect(formatter(100 * 1024)).toBe('100KiB/s');
expect(formatter(Math.pow(1024, 2))).toBe('1MiB/s');
expect(formatter(Math.pow(1024, 3))).toBe('1GiB/s');
expect(formatter(Math.pow(1024, 4))).toBe('1TiB/s');
expect(formatter(Math.pow(1024, 5))).toBe('1PiB/s');
expect(formatter(Math.pow(1024, 6))).toBe('1EiB/s');
expect(formatter(Math.pow(1024, 7))).toBe('1ZiB/s');
expect(formatter(Math.pow(1024, 8))).toBe('1YiB/s');
expect(formatter(Math.pow(1024, 9))).toBe('1024YiB/s');
expect(formatter(Math.pow(1024, 10))).toBe('1048576YiB/s');
});
test('formats bytes in human readable format with additional decimals option', () => {
const formatter0decimals = createMemoryFormatter({ decimals: 0 });
expect(formatter0decimals(0)).toBe('0B');
@@ -43,7 +43,7 @@
"@storybook/types": "8.4.7",
"@types/react-loadable": "^5.5.11",
"antd": "4.10.3",
"core-js": "3.40.0",
"core-js": "3.39.0",
"gh-pages": "^6.2.0",
"jquery": "^3.7.1",
"memoize-one": "^5.2.1",
@@ -54,7 +54,7 @@
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/preset-env": "^7.26.7",
"@babel/preset-env": "^7.23.9",
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.23.3",
"@storybook/react-webpack5": "8.2.9",
@@ -17,10 +17,9 @@
* under the License.
*/
import { useState, ReactNode, SyntheticEvent } from 'react';
import { useState, ReactNode } from 'react';
import { styled } from '@superset-ui/core';
import type { Decorator } from '@storybook/react';
import { ResizeCallbackData } from 'react-resizable';
import type { DecoratorFunction } from '@storybook/types';
import ResizablePanel, { Size } from './ResizablePanel';
export const SupersetBody = styled.div`
@@ -48,9 +47,7 @@ export default function ResizableChartDemo({
<SupersetBody>
<ResizablePanel
initialSize={initialSize}
onResize={(e: SyntheticEvent, data: ResizeCallbackData) =>
setSize(data.size)
}
onResize={(e, data) => setSize(data.size)}
>
{children({
width: size.width - panelPadding,
@@ -61,10 +58,10 @@ export default function ResizableChartDemo({
);
}
export const withResizableChartDemo: Decorator<{
width: number;
height: number;
}> = (storyFn, context) => {
export const withResizableChartDemo: DecoratorFunction<ReactNode> = (
storyFn,
context,
) => {
const {
parameters: { initialSize, panelPadding },
} = context;
@@ -17,7 +17,7 @@
* under the License.
*/
import { PropsWithChildren, ReactNode, SyntheticEvent } from 'react';
import { PropsWithChildren, ReactNode } from 'react';
import {
ResizableBox,
ResizableBoxProps,
@@ -48,7 +48,7 @@ export default function ResizablePanel({
minConstraints={minConstraints}
onResize={
onResize
? (e: SyntheticEvent, data: ResizeCallbackData) => {
? (e, data) => {
const { size } = data;
onResize(e, { ...data, size });
}
@@ -29,7 +29,7 @@ export default {
decorators: [withResizableChartDemo],
};
export const basic = ({ width, height }: { width: number; height: number }) => (
export const basic = ({ width, height }) => (
<SuperChart
chartType={VizType.Chord}
width={width}
@@ -42,16 +42,10 @@ export default {
};
function generateData(geojson: JsonObject) {
return geojson.features.map(
(feat: {
properties: { ISO: string };
type: string;
geometry: JsonObject;
}) => ({
metric: Math.round(Number(seed(feat.properties.ISO)()) * 10000) / 100,
country_id: feat.properties.ISO,
}),
);
return geojson.features.map(feat => ({
metric: Math.round(Number(seed(feat.properties.ISO)()) * 10000) / 100,
country_id: feat.properties.ISO,
}));
}
export const BasicCountryMapStory = (
@@ -70,7 +64,7 @@ export const BasicCountryMapStory = (
useEffect(() => {
const controller = new AbortController();
const { signal } = controller;
fetch(countries[country as keyof typeof countries], { signal })
fetch(countries[country], { signal })
.then(resp => resp.json())
.then(geojson => {
setData(generateData(geojson));
@@ -17,9 +17,9 @@
* under the License.
*/
import { EchartsBoxPlotChartPlugin } from '@superset-ui/plugin-chart-echarts';
import { BoxPlotChartPlugin } from '@superset-ui/legacy-preset-chart-nvd3';
new EchartsBoxPlotChartPlugin().configure({ key: 'box-plot' }).register();
new BoxPlotChartPlugin().configure({ key: 'box-plot' }).register();
export default {
title: 'Legacy Chart Plugins/legacy-preset-chart-nvd3/BoxPlot',
@@ -37,7 +37,7 @@ export default {
decorators: [withResizableChartDemo],
};
export const Gauge = ({ width, height }: { width: number; height: number }) => (
export const Gauge = ({ width, height }) => (
<SuperChart
chartType="echarts-gauge"
width={width}

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