mirror of
https://github.com/apache/superset.git
synced 2026-05-05 07:54:17 +00:00
Compare commits
8 Commits
fix-webpac
...
orval
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6a601ceaa | ||
|
|
86afbc47f5 | ||
|
|
3659f16c00 | ||
|
|
5c9e3f78ce | ||
|
|
2b9e3d11dd | ||
|
|
9593b516d3 | ||
|
|
9762673a97 | ||
|
|
b751b61c3b |
14
.github/CODEOWNERS
vendored
14
.github/CODEOWNERS
vendored
@@ -33,10 +33,10 @@
|
||||
|
||||
# Notify PMC members of changes to extension-related files
|
||||
|
||||
/superset-core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset-extensions-cli/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset/core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset/extensions/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset-frontend/src/packages/superset-core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset-frontend/src/core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset-frontend/src/extensions/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset-core/ @michael-s-molina @villebro
|
||||
/superset-extensions-cli/ @michael-s-molina @villebro
|
||||
/superset/core/ @michael-s-molina @villebro
|
||||
/superset/extensions/ @michael-s-molina @villebro
|
||||
/superset-frontend/src/packages/superset-core/ @michael-s-molina @villebro
|
||||
/superset-frontend/src/core/ @michael-s-molina @villebro
|
||||
/superset-frontend/src/extensions/ @michael-s-molina @villebro
|
||||
|
||||
70
.github/workflows/bashlib.sh
vendored
70
.github/workflows/bashlib.sh
vendored
@@ -182,76 +182,6 @@ cypress-run-all() {
|
||||
kill $flaskProcessId
|
||||
}
|
||||
|
||||
playwright-install() {
|
||||
cd "$GITHUB_WORKSPACE/superset-frontend"
|
||||
|
||||
say "::group::Install Playwright browsers"
|
||||
npx playwright install --with-deps chromium
|
||||
# Create output directories for test results and debugging
|
||||
mkdir -p playwright-results
|
||||
mkdir -p test-results
|
||||
say "::endgroup::"
|
||||
}
|
||||
|
||||
playwright-run() {
|
||||
local APP_ROOT=$1
|
||||
|
||||
# Start Flask from the project root (same as Cypress)
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
local flasklog="${HOME}/flask-playwright.log"
|
||||
local port=8081
|
||||
PLAYWRIGHT_BASE_URL="http://localhost:${port}"
|
||||
if [ -n "$APP_ROOT" ]; then
|
||||
export SUPERSET_APP_ROOT=$APP_ROOT
|
||||
PLAYWRIGHT_BASE_URL=${PLAYWRIGHT_BASE_URL}${APP_ROOT}/
|
||||
fi
|
||||
export PLAYWRIGHT_BASE_URL
|
||||
|
||||
nohup flask run --no-debugger -p $port >"$flasklog" 2>&1 </dev/null &
|
||||
local flaskProcessId=$!
|
||||
|
||||
# Ensure cleanup on exit
|
||||
trap "kill $flaskProcessId 2>/dev/null || true" EXIT
|
||||
|
||||
# Wait for server to be ready with health check
|
||||
local timeout=60
|
||||
say "Waiting for Flask server to start on port $port..."
|
||||
while [ $timeout -gt 0 ]; do
|
||||
if curl -f ${PLAYWRIGHT_BASE_URL}/health >/dev/null 2>&1; then
|
||||
say "Flask server is ready"
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
timeout=$((timeout - 1))
|
||||
done
|
||||
|
||||
if [ $timeout -eq 0 ]; then
|
||||
echo "::error::Flask server failed to start within 60 seconds"
|
||||
echo "::group::Flask startup log"
|
||||
cat "$flasklog"
|
||||
echo "::endgroup::"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Change to frontend directory for Playwright execution
|
||||
cd "$GITHUB_WORKSPACE/superset-frontend"
|
||||
|
||||
say "::group::Run Playwright tests"
|
||||
echo "Running Playwright with baseURL: ${PLAYWRIGHT_BASE_URL}"
|
||||
npx playwright test auth/login --reporter=github --output=playwright-results
|
||||
local status=$?
|
||||
say "::endgroup::"
|
||||
|
||||
# After job is done, print out Flask log for debugging
|
||||
echo "::group::Flask log for Playwright run"
|
||||
cat "$flasklog"
|
||||
echo "::endgroup::"
|
||||
# make sure the program exits
|
||||
kill $flaskProcessId
|
||||
|
||||
return $status
|
||||
}
|
||||
|
||||
eyes-storybook-dependencies() {
|
||||
say "::group::install eyes-storyook dependencies"
|
||||
sudo apt-get update -y && sudo apt-get -y install gconf-service ca-certificates libxshmfence-dev fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 libglib2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libnspr4 libnss3 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release xdg-utils libappindicator1
|
||||
|
||||
2
.github/workflows/bump-python-package.yml
vendored
2
.github/workflows/bump-python-package.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: master
|
||||
|
||||
2
.github/workflows/cancel_duplicates.yml
vendored
2
.github/workflows/cancel_duplicates.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
if: steps.check_queued.outputs.count >= 20
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cancel duplicate workflow runs
|
||||
if: steps.check_queued.outputs.count >= 20
|
||||
|
||||
2
.github/workflows/check-python-deps.yml
vendored
2
.github/workflows/check-python-deps.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
- name: Check and notify
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
|
||||
2
.github/workflows/claude.yml
vendored
2
.github/workflows/claude.yml
vendored
@@ -71,7 +71,7 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
|
||||
4
.github/workflows/dependency-review.yml
vendored
4
.github/workflows/dependency-review.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
- name: "Dependency Review"
|
||||
uses: actions/dependency-review-action@v4
|
||||
continue-on-error: true
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
|
||||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
|
||||
2
.github/workflows/embedded-sdk-release.yml
vendored
2
.github/workflows/embedded-sdk-release.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
run:
|
||||
working-directory: superset-embedded-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: './superset-embedded-sdk/.nvmrc'
|
||||
|
||||
2
.github/workflows/embedded-sdk-test.yml
vendored
2
.github/workflows/embedded-sdk-test.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
run:
|
||||
working-directory: superset-embedded-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: './superset-embedded-sdk/.nvmrc'
|
||||
|
||||
4
.github/workflows/ephemeral-env.yml
vendored
4
.github/workflows/ephemeral-env.yml
vendored
@@ -160,7 +160,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ needs.ephemeral-env-label.outputs.sha }} : ${{steps.get-sha.outputs.sha}} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.ephemeral-env-label.outputs.sha }}
|
||||
persist-credentials: false
|
||||
@@ -220,7 +220,7 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
4
.github/workflows/generate-FOSSA-report.yml
vendored
4
.github/workflows/generate-FOSSA-report.yml
vendored
@@ -27,12 +27,12 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "11"
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
2
.github/workflows/issue_creation.yml
vendored
2
.github/workflows/issue_creation.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
2
.github/workflows/latest-release-tag.yml
vendored
2
.github/workflows/latest-release-tag.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
4
.github/workflows/license-check.yml
vendored
4
.github/workflows/license-check.yml
vendored
@@ -15,12 +15,12 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
|
||||
2
.github/workflows/pr-lint.yml
vendored
2
.github/workflows/pr-lint.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
python-version: ["current", "previous", "next"]
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/prefer-typescript.yml
vendored
2
.github/workflows/prefer-typescript.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
name: Bump version and publish package(s)
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# pulls all commits (needed for lerna / semantic release to correctly version)
|
||||
fetch-depth: 0
|
||||
|
||||
24
.github/workflows/showtime-trigger.yml
vendored
24
.github/workflows/showtime-trigger.yml
vendored
@@ -61,8 +61,17 @@ jobs:
|
||||
console.log(`📊 Permission level for ${actor}: ${permission.permission}`);
|
||||
const authorized = ['write', 'admin'].includes(permission.permission);
|
||||
|
||||
// If this is a synchronize event from unauthorized user, check if Showtime is active and set blocked label
|
||||
if (!authorized && context.eventName === 'pull_request_target' && context.payload.action === 'synchronize') {
|
||||
if (!authorized) {
|
||||
console.log(`🚨 Unauthorized user ${actor} - skipping all operations`);
|
||||
core.setOutput('authorized', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ Authorized maintainer: ${actor}`);
|
||||
core.setOutput('authorized', 'true');
|
||||
|
||||
// If this is a synchronize event, check if Showtime is active and set blocked label
|
||||
if (context.eventName === 'pull_request_target' && context.payload.action === 'synchronize') {
|
||||
console.log(`🔒 Synchronize event detected - checking if Showtime is active`);
|
||||
|
||||
// Check if PR has any circus tent labels (Showtime is in use)
|
||||
@@ -90,15 +99,6 @@ jobs:
|
||||
}
|
||||
}
|
||||
|
||||
if (!authorized) {
|
||||
console.log(`🚨 Unauthorized user ${actor} - skipping all operations`);
|
||||
core.setOutput('authorized', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ Authorized maintainer: ${actor}`);
|
||||
core.setOutput('authorized', 'true');
|
||||
|
||||
- name: Install Superset Showtime
|
||||
if: steps.auth.outputs.authorized == 'true'
|
||||
run: |
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
|
||||
- name: Checkout PR code (only if build needed)
|
||||
if: steps.auth.outputs.authorized == 'true' && steps.check.outputs.build_needed == 'true'
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ steps.check.outputs.target_sha }}
|
||||
persist-credentials: false
|
||||
|
||||
2
.github/workflows/superset-app-cli.yml
vendored
2
.github/workflows/superset-app-cli.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
4
.github/workflows/superset-docs-deploy.yml
vendored
4
.github/workflows/superset-docs-deploy.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
node-version-file: './docs/.nvmrc'
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
- uses: actions/setup-java@v5
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '21'
|
||||
|
||||
4
.github/workflows/superset-docs-verify.yml
vendored
4
.github/workflows/superset-docs-verify.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
name: Link Checking
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
# Do not bump this linkinator-action version without opening
|
||||
# an ASF Infra ticket to allow the new version first!
|
||||
- uses: JustinBeckwith/linkinator-action@v1.11.0
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
working-directory: docs
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
6
.github/workflows/superset-e2e.yml
vendored
6
.github/workflows/superset-e2e.yml
vendored
@@ -69,21 +69,21 @@ jobs:
|
||||
# Conditional checkout based on context
|
||||
- name: Checkout for push or pull_request event
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Checkout using ref (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
submodules: recursive
|
||||
- name: Checkout using PR ID (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
working-directory: superset-extensions-cli
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
if: steps.check.outputs.superset-extensions-cli
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
flags: superset-extensions-cli
|
||||
|
||||
12
.github/workflows/superset-frontend.yml
vendored
12
.github/workflows/superset-frontend.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
should-run: ${{ steps.check.outputs.frontend }}
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
@@ -101,7 +101,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Download Coverage Artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: coverage-artifacts-*
|
||||
path: coverage/
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
- name: tsc
|
||||
run: |
|
||||
docker run --rm $TAG bash -c \
|
||||
"npm run plugins:build && npm run type"
|
||||
"npm run type"
|
||||
|
||||
validate-frontend:
|
||||
needs: frontend-build
|
||||
@@ -151,7 +151,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
|
||||
2
.github/workflows/superset-helm-lint.yml
vendored
2
.github/workflows/superset-helm-lint.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/superset-helm-release.yml
vendored
2
.github/workflows/superset-helm-release.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref_name }}
|
||||
persist-credentials: true
|
||||
|
||||
141
.github/workflows/superset-playwright.yml
vendored
141
.github/workflows/superset-playwright.yml
vendored
@@ -1,141 +0,0 @@
|
||||
name: Playwright E2E Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
- "[0-9].[0-9]*"
|
||||
pull_request:
|
||||
types: [synchronize, opened, reopened, ready_for_review]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'The branch or tag to checkout'
|
||||
required: false
|
||||
default: ''
|
||||
pr_id:
|
||||
description: 'The pull request ID to checkout'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
playwright-tests:
|
||||
runs-on: ubuntu-22.04
|
||||
# Allow workflow to succeed even if tests fail during shadow mode
|
||||
continue-on-error: true
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
browser: ["chromium"]
|
||||
app_root: ["", "/app/prefix"]
|
||||
env:
|
||||
SUPERSET_ENV: development
|
||||
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
|
||||
SUPERSET__SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://superset:superset@127.0.0.1:15432/superset
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
REDIS_PORT: 16379
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_USER: superset
|
||||
POSTGRES_PASSWORD: superset
|
||||
ports:
|
||||
- 15432:5432
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- 16379:6379
|
||||
steps:
|
||||
# -------------------------------------------------------
|
||||
# Conditional checkout based on context (same as Cypress workflow)
|
||||
- name: Checkout for push or pull_request event
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Checkout using ref (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
submodules: recursive
|
||||
- name: Checkout using PR ID (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
submodules: recursive
|
||||
# -------------------------------------------------------
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
- name: Setup postgres
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: setup-postgres
|
||||
- name: Import test data
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: testdata
|
||||
- name: Setup Node.js
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install npm dependencies
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: npm-install
|
||||
- name: Build javascript packages
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: build-instrumented-assets
|
||||
- name: Install Playwright
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: playwright-install
|
||||
- name: Run Playwright
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=4096"
|
||||
with:
|
||||
run: playwright-run ${{ matrix.app_root }}
|
||||
- name: Set safe app root
|
||||
if: failure()
|
||||
id: set-safe-app-root
|
||||
run: |
|
||||
APP_ROOT="${{ matrix.app_root }}"
|
||||
SAFE_APP_ROOT=${APP_ROOT//\//_}
|
||||
echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT
|
||||
- name: Upload Playwright Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/superset-frontend/playwright-results/
|
||||
${{ github.workspace }}/superset-frontend/test-results/
|
||||
name: playwright-artifact-${{ github.run_id }}-${{ github.job }}-${{ matrix.browser }}--${{ steps.set-safe-app-root.outputs.safe_app_root }}
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -152,7 +152,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
4
.github/workflows/superset-translations.yml
vendored
4
.github/workflows/superset-translations.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/superset-websocket.yml
vendored
2
.github/workflows/superset-websocket.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install dependencies
|
||||
|
||||
2
.github/workflows/supersetbot.yml
vendored
2
.github/workflows/supersetbot.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
});
|
||||
|
||||
- name: "Checkout ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
4
.github/workflows/tag-release.yml
vendored
4
.github/workflows/tag-release.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
2
.github/workflows/tech-debt.yml
vendored
2
.github/workflows/tech-debt.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
name: Generate Reports
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
2
.github/workflows/welcome-new-users.yml
vendored
2
.github/workflows/welcome-new-users.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Welcome Message
|
||||
uses: actions/first-interaction@v3
|
||||
uses: actions/first-interaction@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
repo-token: ${{ github.token }}
|
||||
|
||||
@@ -49,7 +49,7 @@ repos:
|
||||
hooks:
|
||||
- id: check-docstring-first
|
||||
- id: check-added-large-files
|
||||
exclude: ^.*\.(geojson)$|^docs/static/img/screenshots/.*|^superset-frontend/CHANGELOG\.md$
|
||||
exclude: ^.*\.(geojson)$|^docs/static/img/screenshots/.*|^superset-frontend/CHANGELOG\.md$|^superset-frontend/openapi-raw\.json$|^docs/static/resources/openapi\.json$|^superset/static/openapi\.json$
|
||||
- id: check-yaml
|
||||
exclude: ^helm/superset/templates/
|
||||
- id: debug-statements
|
||||
|
||||
@@ -73,7 +73,6 @@ ibm-db2.svg
|
||||
postgresql.svg
|
||||
snowflake.svg
|
||||
ydb.svg
|
||||
loading.svg
|
||||
|
||||
# docs-related
|
||||
erd.puml
|
||||
|
||||
30
LLMS.md
30
LLMS.md
@@ -15,9 +15,8 @@ Apache Superset is a data visualization platform with Flask/Python backend and R
|
||||
|
||||
### Testing Strategy Migration
|
||||
- **Prefer unit tests** over integration tests
|
||||
- **Prefer integration tests** over end-to-end tests
|
||||
- **Use Playwright for E2E tests** - Migrating from Cypress
|
||||
- **Cypress is deprecated** - Will be removed once migration is completed
|
||||
- **Prefer integration tests** over Cypress end-to-end tests
|
||||
- **Cypress is last resort** - Actively moving away from Cypress
|
||||
- **Use Jest + React Testing Library** for component testing
|
||||
- **Use `test()` instead of `describe()`** - Follow [avoid nesting when testing](https://kentcdodds.com/blog/avoid-nesting-when-youre-testing) principles
|
||||
|
||||
@@ -108,18 +107,6 @@ superset/
|
||||
npm run test # All tests
|
||||
npm run test -- filename.test.tsx # Single file
|
||||
|
||||
# E2E Tests (Playwright - NEW)
|
||||
npm run playwright:test # All Playwright tests
|
||||
npm run playwright:ui # Interactive UI mode
|
||||
npm run playwright:headed # See browser during tests
|
||||
npx playwright test tests/auth/login.spec.ts # Single file
|
||||
npm run playwright:debug tests/auth/login.spec.ts # Debug specific file
|
||||
|
||||
# E2E Tests (Cypress - DEPRECATED)
|
||||
cd superset-frontend/cypress-base
|
||||
npm run cypress-run-chrome # All Cypress tests (headless)
|
||||
npm run cypress-debug # Interactive Cypress UI
|
||||
|
||||
# Backend
|
||||
pytest # All tests
|
||||
pytest tests/unit_tests/specific_test.py # Single file
|
||||
@@ -149,19 +136,6 @@ curl -f http://localhost:8088/health || echo "❌ Setup required - see https://s
|
||||
- **Use negation operator**: `~Model.field` instead of `== False` to avoid ruff E712 errors
|
||||
- **Example**: `~Model.is_active` instead of `Model.is_active == False`
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
**When creating pull requests:**
|
||||
|
||||
1. **Read the current PR template**: Always check `.github/PULL_REQUEST_TEMPLATE.md` for the latest format
|
||||
2. **Use the template sections**: Include all sections from the template (SUMMARY, BEFORE/AFTER, TESTING INSTRUCTIONS, ADDITIONAL INFORMATION)
|
||||
3. **Follow PR title conventions**: Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||
- Format: `type(scope): description`
|
||||
- Example: `fix(dashboard): load charts correctly`
|
||||
- Types: `fix`, `feat`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`
|
||||
|
||||
**Important**: Always reference the actual template file at `.github/PULL_REQUEST_TEMPLATE.md` instead of using cached content, as the template may be updated over time.
|
||||
|
||||
## Pre-commit Validation
|
||||
|
||||
**Use pre-commit hooks for quality validation:**
|
||||
|
||||
@@ -23,8 +23,6 @@ This file documents any backwards-incompatible changes in Superset and
|
||||
assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
- [33055](https://github.com/apache/superset/pull/33055): Upgrades Flask-AppBuilder to 5.0.0. The AUTH_OID authentication type has been deprecated and is no longer available as an option in Flask-AppBuilder. OpenID (OID) is considered a deprecated authentication protocol - if you are using AUTH_OID, you will need to migrate to an alternative authentication method such as OAuth, LDAP, or database authentication before upgrading.
|
||||
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
|
||||
- [34871](https://github.com/apache/superset/pull/34871): Fixed Jest test hanging issue from Ant Design v5 upgrade. MessageChannel is now mocked in test environment to prevent rc-overflow from causing Jest to hang. Test environment only - no production impact.
|
||||
- [34782](https://github.com/apache/superset/pull/34782): Dataset exports now include the dataset ID in their file name (similar to charts and dashboards). If managing assets as code, make sure to rename existing dataset YAMLs to include the ID (and avoid duplicated files).
|
||||
- [34536](https://github.com/apache/superset/pull/34536): The `ENVIRONMENT_TAG_CONFIG` color values have changed to support only Ant Design semantic colors. Update your `superset_config.py`:
|
||||
|
||||
@@ -118,8 +118,6 @@ services:
|
||||
POSTGRES_DB: superset_light
|
||||
SUPERSET__SQLALCHEMY_EXAMPLES_URI: "duckdb:////app/data/examples.duckdb"
|
||||
SUPERSET_CONFIG_PATH: /app/docker/pythonpath_dev/superset_config_docker_light.py
|
||||
GITHUB_HEAD_REF: ${GITHUB_HEAD_REF:-}
|
||||
GITHUB_SHA: ${GITHUB_SHA:-}
|
||||
|
||||
superset-init-light:
|
||||
build:
|
||||
@@ -162,11 +160,8 @@ services:
|
||||
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
|
||||
# configuring the dev-server to use the host.docker.internal to connect to the backend
|
||||
superset: "http://superset-light:8088"
|
||||
# Webpack dev server configuration
|
||||
WEBPACK_DEVSERVER_HOST: "${WEBPACK_DEVSERVER_HOST:-0.0.0.0}"
|
||||
WEBPACK_DEVSERVER_PORT: "${WEBPACK_DEVSERVER_PORT:-9000}"
|
||||
ports:
|
||||
- "${NODE_PORT:-9001}:9000" # Parameterized port, accessible on all interfaces
|
||||
- "127.0.0.1:${NODE_PORT:-9001}:9000" # Parameterized port
|
||||
command: ["/app/docker/docker-frontend.sh"]
|
||||
env_file:
|
||||
- path: docker/.env # default
|
||||
|
||||
@@ -138,7 +138,7 @@ try:
|
||||
from superset_config_docker import * # noqa: F403
|
||||
|
||||
logger.info(
|
||||
"Loaded your Docker configuration at [%s]", superset_config_docker.__file__
|
||||
f"Loaded your Docker configuration at [{superset_config_docker.__file__}]"
|
||||
)
|
||||
except ImportError:
|
||||
logger.info("Using default Docker config...")
|
||||
|
||||
@@ -12,7 +12,7 @@ Users can configure automated alerts and reports to send dashboards or charts to
|
||||
- *Alerts* are sent when a SQL condition is reached
|
||||
- *Reports* are sent on a schedule
|
||||
|
||||
Alerts and reports are disabled by default. To turn them on, you'll need to change configuration settings and install a suitable headless browser in your environment.
|
||||
Alerts and reports are disabled by default. To turn them on, you need to do some setup, described here.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -35,14 +35,16 @@ Screenshots will be taken but no messages actually sent as long as `ALERT_REPORT
|
||||
|
||||
#### In your `Dockerfile`
|
||||
|
||||
You'll need to extend the Superset image to include a headless browser. Your options include:
|
||||
- Use Playwright with Chrome: this is the recommended approach as of version >=4.1.x. A working example of a Dockerfile that installs these tools is provided under “Building your own production Docker image” on the [Docker Builds](/docs/installation/docker-builds#building-your-own-production-docker-image) page. Read the code comments there as you'll also need to change a feature flag in your config.
|
||||
- Use Firefox: you'll need to install geckodriver and Firefox.
|
||||
- Use Chrome without Playwright: you'll need to install Chrome and set the value of `WEBDRIVER_TYPE` to `"chrome"` in your `superset_config.py`.
|
||||
- You must install a headless browser, for taking screenshots of the charts and dashboards. Only Firefox and Chrome are currently supported.
|
||||
> If you choose Chrome, you must also change the value of `WEBDRIVER_TYPE` to `"chrome"` in your `superset_config.py`.
|
||||
|
||||
In Superset versions <=4.0x, users installed Firefox or Chrome and that was documented here.
|
||||
Note: All the components required (Firefox headless browser, Redis, Postgres db, celery worker and celery beat) are present in the *dev* docker image if you are following [Installing Superset Locally](/docs/installation/docker-compose/).
|
||||
All you need to do is add the required config variables described in this guide (See `Detailed Config`).
|
||||
|
||||
Only the worker container needs the browser.
|
||||
If you are running a non-dev docker image, e.g., a stable release like `apache/superset:3.1.0`, that image does not include a headless browser. Only the `superset_worker` container needs this headless browser to browse to the target chart or dashboard.
|
||||
You can either install and configure the headless browser - see "Custom Dockerfile" section below - or when deploying via `docker compose`, modify your `docker-compose.yml` file to use a dev image for the worker container and a stable release image for the `superset_app` container.
|
||||
|
||||
*Note*: In this context, a "dev image" is the same application software as its corresponding non-dev image, just bundled with additional tools. So an image like `3.1.0-dev` is identical to `3.1.0` when it comes to stability, functionality, and running in production. The actual "in-development" versions of Superset - cutting-edge and unstable - are not tagged with version numbers on Docker Hub and will display version `0.0.0-dev` within the Superset UI.
|
||||
|
||||
### Slack integration
|
||||
|
||||
@@ -150,8 +152,8 @@ SMTP_MAIL_FROM = "noreply@youremail.com"
|
||||
EMAIL_REPORTS_SUBJECT_PREFIX = "[Superset] " # optional - overwrites default value in config.py of "[Report] "
|
||||
|
||||
# WebDriver configuration
|
||||
# If you use Firefox or Playwright with Chrome, you can stick with default values
|
||||
# If you use Chrome and are *not* using Playwright, then add the following WEBDRIVER_TYPE and WEBDRIVER_OPTION_ARGS
|
||||
# If you use Firefox, you can stick with default values
|
||||
# If you use Chrome, then add the following WEBDRIVER_TYPE and WEBDRIVER_OPTION_ARGS
|
||||
WEBDRIVER_TYPE = "chrome"
|
||||
WEBDRIVER_OPTION_ARGS = [
|
||||
"--force-device-scale-factor=2.0",
|
||||
@@ -217,6 +219,62 @@ def alert_dynamic_minimal_interval(**kwargs) -> int:
|
||||
ALERT_MINIMUM_INTERVAL = alert_dynamic_minimal_interval
|
||||
```
|
||||
|
||||
## Custom Dockerfile
|
||||
|
||||
If you're running the dev version of a released Superset image, like `apache/superset:3.1.0-dev`, you should be set with the above.
|
||||
|
||||
But if you're building your own image, or starting with a non-dev version, a webdriver (and headless browser) is needed to capture screenshots of the charts and dashboards which are then sent to the recipient.
|
||||
Here's how you can modify your Dockerfile to take the screenshots either with Firefox or Chrome.
|
||||
|
||||
### Using Firefox
|
||||
|
||||
```docker
|
||||
FROM apache/superset:3.1.0
|
||||
|
||||
USER root
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install --no-install-recommends -y firefox-esr
|
||||
|
||||
ENV GECKODRIVER_VERSION=0.29.0
|
||||
RUN wget -q https://github.com/mozilla/geckodriver/releases/download/v${GECKODRIVER_VERSION}/geckodriver-v${GECKODRIVER_VERSION}-linux64.tar.gz && \
|
||||
tar -x geckodriver -zf geckodriver-v${GECKODRIVER_VERSION}-linux64.tar.gz -O > /usr/bin/geckodriver && \
|
||||
chmod 755 /usr/bin/geckodriver && \
|
||||
rm geckodriver-v${GECKODRIVER_VERSION}-linux64.tar.gz
|
||||
|
||||
RUN pip install --no-cache gevent psycopg2 redis
|
||||
|
||||
USER superset
|
||||
```
|
||||
|
||||
### Using Chrome
|
||||
|
||||
```docker
|
||||
FROM apache/superset:3.1.0
|
||||
|
||||
USER root
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y wget zip libaio1
|
||||
|
||||
RUN export CHROMEDRIVER_VERSION=$(curl --silent https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_116) && \
|
||||
wget -O google-chrome-stable_current_amd64.deb -q http://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROMEDRIVER_VERSION}-1_amd64.deb && \
|
||||
apt-get install -y --no-install-recommends ./google-chrome-stable_current_amd64.deb && \
|
||||
rm -f google-chrome-stable_current_amd64.deb
|
||||
|
||||
RUN export CHROMEDRIVER_VERSION=$(curl --silent https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_116) && \
|
||||
wget -q https://storage.googleapis.com/chrome-for-testing-public/${CHROMEDRIVER_VERSION}/linux64/chromedriver-linux64.zip && \
|
||||
unzip -j chromedriver-linux64.zip -d /usr/bin && \
|
||||
chmod 755 /usr/bin/chromedriver && \
|
||||
rm -f chromedriver-linux64.zip
|
||||
|
||||
RUN pip install --no-cache gevent psycopg2 redis
|
||||
|
||||
USER superset
|
||||
```
|
||||
|
||||
Don't forget to set `WEBDRIVER_TYPE` and `WEBDRIVER_OPTION_ARGS` in your config if you use Chrome.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
There are many reasons that reports might not be working. Try these steps to check for specific issues.
|
||||
@@ -235,7 +293,9 @@ This is the best source of information about the problem. In a docker compose d
|
||||
|
||||
To take a screenshot, the worker visits the dashboard or chart using a headless browser, then takes a screenshot. If you are able to send a chart as CSV or text but can't send as PNG, your problem may lie with the browser.
|
||||
|
||||
If you are handling the installation of the headless browser on your own, do your own verification to ensure that the headless browser opens successfully in the worker environment.
|
||||
Superset docker images that have a tag ending with `-dev` have the Firefox headless browser and geckodriver already installed. You can test that these are installed and in the proper path by entering your Superset worker and running `firefox --headless` and then `geckodriver`. Both commands should start those applications.
|
||||
|
||||
If you are handling the installation of that software on your own, or wish to use Chromium instead, do your own verification to ensure that the headless browser opens successfully in the worker environment.
|
||||
|
||||
### Send a test email
|
||||
|
||||
|
||||
@@ -363,6 +363,110 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
|
||||
]
|
||||
```
|
||||
|
||||
### Keycloak-Specific Configuration using Flask-OIDC
|
||||
|
||||
If you are using Keycloak as OpenID Connect 1.0 Provider, the above configuration based on [`Authlib`](https://authlib.org/) might not work. In this case using [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is a viable option.
|
||||
|
||||
Make sure the pip package [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is installed on the webserver. This was successfully tested using version 2.2.0. This package requires [`Flask-OpenID`](https://pypi.org/project/Flask-OpenID/) as a dependency.
|
||||
|
||||
The following code defines a new security manager. Add it to a new file named `keycloak_security_manager.py`, placed in the same directory as your `superset_config.py` file.
|
||||
|
||||
```python
|
||||
from flask_appbuilder.security.manager import AUTH_OID
|
||||
from superset.security import SupersetSecurityManager
|
||||
from flask_oidc import OpenIDConnect
|
||||
from flask_appbuilder.security.views import AuthOIDView
|
||||
from flask_login import login_user
|
||||
from urllib.parse import quote
|
||||
from flask_appbuilder.views import ModelView, SimpleFormView, expose
|
||||
from flask import (
|
||||
redirect,
|
||||
request
|
||||
)
|
||||
import logging
|
||||
|
||||
class OIDCSecurityManager(SupersetSecurityManager):
|
||||
|
||||
def __init__(self, appbuilder):
|
||||
super(OIDCSecurityManager, self).__init__(appbuilder)
|
||||
if self.auth_type == AUTH_OID:
|
||||
self.oid = OpenIDConnect(self.appbuilder.get_app)
|
||||
self.authoidview = AuthOIDCView
|
||||
|
||||
class AuthOIDCView(AuthOIDView):
|
||||
|
||||
@expose('/login/', methods=['GET', 'POST'])
|
||||
def login(self, flag=True):
|
||||
sm = self.appbuilder.sm
|
||||
oidc = sm.oid
|
||||
|
||||
@self.appbuilder.sm.oid.require_login
|
||||
def handle_login():
|
||||
user = sm.auth_user_oid(oidc.user_getfield('email'))
|
||||
|
||||
if user is None:
|
||||
info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email'])
|
||||
user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'),
|
||||
info.get('email'), sm.find_role('Gamma'))
|
||||
|
||||
login_user(user, remember=False)
|
||||
return redirect(self.appbuilder.get_url_for_index)
|
||||
|
||||
return handle_login()
|
||||
|
||||
@expose('/logout/', methods=['GET', 'POST'])
|
||||
def logout(self):
|
||||
oidc = self.appbuilder.sm.oid
|
||||
|
||||
oidc.logout()
|
||||
super(AuthOIDCView, self).logout()
|
||||
redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login
|
||||
|
||||
return redirect(
|
||||
oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?redirect_uri=' + quote(redirect_url))
|
||||
```
|
||||
|
||||
Then add to your `superset_config.py` file:
|
||||
|
||||
```python
|
||||
from keycloak_security_manager import OIDCSecurityManager
|
||||
from flask_appbuilder.security.manager import AUTH_OID, AUTH_REMOTE_USER, AUTH_DB, AUTH_LDAP, AUTH_OAUTH
|
||||
import os
|
||||
|
||||
AUTH_TYPE = AUTH_OID
|
||||
SECRET_KEY: 'SomethingNotEntirelySecret'
|
||||
OIDC_CLIENT_SECRETS = '/path/to/client_secret.json'
|
||||
OIDC_ID_TOKEN_COOKIE_SECURE = False
|
||||
OIDC_OPENID_REALM: '<myRealm>'
|
||||
OIDC_INTROSPECTION_AUTH_METHOD: 'client_secret_post'
|
||||
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
|
||||
|
||||
# Will allow user self registration, allowing to create Flask users from Authorized User
|
||||
AUTH_USER_REGISTRATION = True
|
||||
|
||||
# The default user self registration role
|
||||
AUTH_USER_REGISTRATION_ROLE = 'Public'
|
||||
```
|
||||
|
||||
Store your client-specific OpenID information in a file called `client_secret.json`. Create this file in the same directory as `superset_config.py`:
|
||||
|
||||
```json
|
||||
{
|
||||
"<myOpenIDProvider>": {
|
||||
"issuer": "https://<myKeycloakDomain>/realms/<myRealm>",
|
||||
"auth_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/auth",
|
||||
"client_id": "https://<myKeycloakDomain>",
|
||||
"client_secret": "<myClientSecret>",
|
||||
"redirect_uris": [
|
||||
"https://<SupersetWebserver>/oauth-authorized/<myOpenIDProvider>"
|
||||
],
|
||||
"userinfo_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/userinfo",
|
||||
"token_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token",
|
||||
"token_introspection_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token/introspect"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## LDAP Authentication
|
||||
|
||||
FAB supports authenticating user credentials against an LDAP server.
|
||||
|
||||
@@ -10,15 +10,8 @@ version: 1
|
||||
## Jinja Templates
|
||||
|
||||
SQL Lab and Explore supports [Jinja templating](https://jinja.palletsprojects.com/en/2.11.x/) in queries.
|
||||
To enable templating, the `ENABLE_TEMPLATE_PROCESSING` [feature flag](/docs/configuration/configuring-superset#feature-flags) needs to be enabled in `superset_config.py`.
|
||||
|
||||
> #### ⚠️ Security Warning
|
||||
>
|
||||
> While powerful, this feature executes template code on the server. Within the Superset security model, this is **intended functionality**, as users with permissions to edit charts and virtual datasets are considered **trusted users**.
|
||||
>
|
||||
> If you grant these permissions to untrusted users, this feature can be exploited as a **Server-Side Template Injection (SSTI)** vulnerability. Do not enable `ENABLE_TEMPLATE_PROCESSING` unless you fully understand and accept the associated security risks.
|
||||
|
||||
When templating is enabled, python code can be embedded in virtual datasets and
|
||||
To enable templating, the `ENABLE_TEMPLATE_PROCESSING` [feature flag](/docs/configuration/configuring-superset#feature-flags) needs to be enabled in
|
||||
`superset_config.py`. When templating is enabled, python code can be embedded in virtual datasets and
|
||||
in Custom SQL in the filter and metric controls in Explore. By default, the following variables are
|
||||
made available in the Jinja context:
|
||||
|
||||
|
||||
@@ -165,206 +165,6 @@ Or in the CRUD interface theme JSON:
|
||||
|
||||
This feature works with the stock Docker image - no custom build required!
|
||||
|
||||
## ECharts Configuration Overrides
|
||||
|
||||
:::note
|
||||
Available since Superset 6.0
|
||||
:::
|
||||
|
||||
Superset provides fine-grained control over ECharts visualizations through theme-level configuration overrides. This allows you to customize the appearance and behavior of all ECharts-based charts without modifying individual chart configurations.
|
||||
|
||||
### Global ECharts Overrides
|
||||
|
||||
Apply settings to all ECharts visualizations using `echartsOptionsOverrides`:
|
||||
|
||||
```python
|
||||
THEME_DEFAULT = {
|
||||
"token": {
|
||||
"colorPrimary": "#2893B3",
|
||||
# ... other Ant Design tokens
|
||||
},
|
||||
"echartsOptionsOverrides": {
|
||||
"grid": {
|
||||
"left": "10%",
|
||||
"right": "10%",
|
||||
"top": "15%",
|
||||
"bottom": "15%"
|
||||
},
|
||||
"tooltip": {
|
||||
"backgroundColor": "rgba(0, 0, 0, 0.8)",
|
||||
"borderColor": "#ccc",
|
||||
"textStyle": {
|
||||
"color": "#fff"
|
||||
}
|
||||
},
|
||||
"legend": {
|
||||
"textStyle": {
|
||||
"fontSize": 14,
|
||||
"fontWeight": "bold"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Chart-Specific Overrides
|
||||
|
||||
Target specific chart types using `echartsOptionsOverridesByChartType`:
|
||||
|
||||
```python
|
||||
THEME_DEFAULT = {
|
||||
"token": {
|
||||
"colorPrimary": "#2893B3",
|
||||
# ... other tokens
|
||||
},
|
||||
"echartsOptionsOverridesByChartType": {
|
||||
"echarts_pie": {
|
||||
"legend": {
|
||||
"orient": "vertical",
|
||||
"right": 10,
|
||||
"top": "center"
|
||||
}
|
||||
},
|
||||
"echarts_timeseries": {
|
||||
"xAxis": {
|
||||
"axisLabel": {
|
||||
"rotate": 45,
|
||||
"fontSize": 12
|
||||
}
|
||||
},
|
||||
"dataZoom": [{
|
||||
"type": "slider",
|
||||
"show": True,
|
||||
"start": 0,
|
||||
"end": 100
|
||||
}]
|
||||
},
|
||||
"echarts_bubble": {
|
||||
"grid": {
|
||||
"left": "15%",
|
||||
"bottom": "20%"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### UI Configuration
|
||||
|
||||
You can also configure ECharts overrides through the theme CRUD interface:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": {
|
||||
"colorPrimary": "#2893B3"
|
||||
},
|
||||
"echartsOptionsOverrides": {
|
||||
"grid": {
|
||||
"left": "10%",
|
||||
"right": "10%"
|
||||
},
|
||||
"tooltip": {
|
||||
"backgroundColor": "rgba(0, 0, 0, 0.8)"
|
||||
}
|
||||
},
|
||||
"echartsOptionsOverridesByChartType": {
|
||||
"echarts_pie": {
|
||||
"legend": {
|
||||
"orient": "vertical",
|
||||
"right": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Override Precedence
|
||||
|
||||
The system applies overrides in the following order (last wins):
|
||||
|
||||
1. **Base ECharts theme** - Default Superset styling
|
||||
2. **Plugin options** - Chart-specific configurations
|
||||
3. **Global overrides** - `echartsOptionsOverrides`
|
||||
4. **Chart-specific overrides** - `echartsOptionsOverridesByChartType[chartType]`
|
||||
|
||||
This ensures chart-specific overrides take precedence over global ones.
|
||||
|
||||
### Common Chart Types
|
||||
|
||||
Available chart types for `echartsOptionsOverridesByChartType`:
|
||||
|
||||
- `echarts_timeseries` - Time series/line charts
|
||||
- `echarts_pie` - Pie and donut charts
|
||||
- `echarts_bubble` - Bubble/scatter charts
|
||||
- `echarts_funnel` - Funnel charts
|
||||
- `echarts_gauge` - Gauge charts
|
||||
- `echarts_radar` - Radar charts
|
||||
- `echarts_boxplot` - Box plot charts
|
||||
- `echarts_treemap` - Treemap charts
|
||||
- `echarts_sunburst` - Sunburst charts
|
||||
- `echarts_graph` - Network/graph charts
|
||||
- `echarts_sankey` - Sankey diagrams
|
||||
- `echarts_heatmap` - Heatmaps
|
||||
- `echarts_mixed_timeseries` - Mixed time series
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Start with global overrides** for consistent styling across all charts
|
||||
2. **Use chart-specific overrides** for unique requirements per visualization type
|
||||
3. **Test thoroughly** as overrides use deep merge - nested objects are combined, but arrays are completely replaced
|
||||
4. **Document your overrides** to help team members understand custom styling
|
||||
5. **Consider performance** - complex overrides may impact chart rendering speed
|
||||
|
||||
### Example: Corporate Branding
|
||||
|
||||
```python
|
||||
# Complete corporate theme with ECharts customization
|
||||
THEME_DEFAULT = {
|
||||
"token": {
|
||||
"colorPrimary": "#1B4D3E",
|
||||
"fontFamily": "Corporate Sans, Arial, sans-serif"
|
||||
},
|
||||
"echartsOptionsOverrides": {
|
||||
"grid": {
|
||||
"left": "8%",
|
||||
"right": "8%",
|
||||
"top": "12%",
|
||||
"bottom": "12%"
|
||||
},
|
||||
"textStyle": {
|
||||
"fontFamily": "Corporate Sans, Arial, sans-serif"
|
||||
},
|
||||
"title": {
|
||||
"textStyle": {
|
||||
"color": "#1B4D3E",
|
||||
"fontSize": 18,
|
||||
"fontWeight": "bold"
|
||||
}
|
||||
}
|
||||
},
|
||||
"echartsOptionsOverridesByChartType": {
|
||||
"echarts_timeseries": {
|
||||
"xAxis": {
|
||||
"axisLabel": {
|
||||
"color": "#666",
|
||||
"fontSize": 11
|
||||
}
|
||||
}
|
||||
},
|
||||
"echarts_pie": {
|
||||
"legend": {
|
||||
"textStyle": {
|
||||
"fontSize": 12
|
||||
},
|
||||
"itemGap": 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This feature provides powerful theming capabilities while maintaining the flexibility of ECharts' extensive configuration options.
|
||||
|
||||
## Advanced Features
|
||||
|
||||
- **System Themes**: Manage system-wide default and dark themes via UI or configuration
|
||||
|
||||
@@ -620,10 +620,10 @@ See [how tos](/docs/contributing/howtos#linting)
|
||||
|
||||
:::tip
|
||||
`act` compatibility of Superset's GHAs is not fully tested. Running `act` locally may or may not
|
||||
work for different actions, and may require fine tuning and local secret-handling.
|
||||
work for different actions, and may require fine tunning and local secret-handling.
|
||||
For those more intricate GHAs that are tricky to run locally, we recommend iterating
|
||||
directly on GHA's infrastructure, by pushing directly on a branch and monitoring GHA logs.
|
||||
For more targeted iteration, see the `gh workflow run --ref {BRANCH}` subcommand of the GitHub CLI.
|
||||
For more targetted iteration, see the `gh workflow run --ref {BRANCH}` subcommand of the GitHub CLI.
|
||||
:::
|
||||
|
||||
For automation and CI/CD, Superset makes extensive use of GitHub Actions (GHA). You
|
||||
@@ -631,7 +631,7 @@ can find all of the workflows and other assets under the `.github/` folder. This
|
||||
|
||||
- running the backend unit test suites (`tests/`)
|
||||
- running the frontend test suites (`superset-frontend/src/**.*.test.*`)
|
||||
- running our Playwright end-to-end tests (`superset-frontend/playwright/`) and legacy Cypress tests (`superset-frontend/cypress-base/`)
|
||||
- running our Cypress end-to-end tests (`superset-frontend/cypress-base/`)
|
||||
- linting the codebase, including all Python, Typescript and Javascript, yaml and beyond
|
||||
- checking for all sorts of other rules conventions
|
||||
|
||||
|
||||
@@ -225,57 +225,21 @@ npm run test -- path/to/file.js
|
||||
|
||||
### E2E Integration Testing
|
||||
|
||||
**Note: We are migrating from Cypress to Playwright. Use Playwright for new tests.**
|
||||
|
||||
#### Playwright (Recommended - NEW)
|
||||
|
||||
For E2E testing with Playwright, use the same `docker compose` backend:
|
||||
For E2E testing, we recommend that you use a `docker compose` backend
|
||||
|
||||
```bash
|
||||
CYPRESS_CONFIG=true docker compose up --build
|
||||
```
|
||||
`docker compose` will get to work and expose a Cypress-ready Superset app.
|
||||
This app uses a different database schema (`superset_cypress`) to keep it isolated from
|
||||
your other dev environmen(s)t, a specific set of examples, and a set of configurations that
|
||||
aligns with the expectations within the end-to-end tests. Also note that it's served on a
|
||||
different port than the default port for the backend (`8088`).
|
||||
|
||||
The backend setup is identical - this exposes a test-ready Superset app on port 8081 with isolated database schema (`superset_cypress`), test data, and configurations.
|
||||
Now in another terminal, let's get ready to execute some Cypress commands. First, tell cypress
|
||||
to connect to the Cypress-ready Superset backend.
|
||||
|
||||
Now in another terminal, run Playwright tests:
|
||||
|
||||
```bash
|
||||
# Navigate to frontend directory (Playwright config is here)
|
||||
cd superset-frontend
|
||||
|
||||
# Run all Playwright tests
|
||||
npm run playwright:test
|
||||
# or: npx playwright test
|
||||
|
||||
# Run with interactive UI for debugging
|
||||
npm run playwright:ui
|
||||
# or: npx playwright test --ui
|
||||
|
||||
# Run in headed mode (see browser)
|
||||
npm run playwright:headed
|
||||
# or: npx playwright test --headed
|
||||
|
||||
# Run specific test file
|
||||
npx playwright test tests/auth/login.spec.ts
|
||||
|
||||
# Run with debug mode (step through tests)
|
||||
npm run playwright:debug tests/auth/login.spec.ts
|
||||
# or: npx playwright test --debug tests/auth/login.spec.ts
|
||||
|
||||
# Generate test report
|
||||
npx playwright show-report
|
||||
```
|
||||
|
||||
Configuration is in `superset-frontend/playwright.config.ts`. Base URL is automatically set to `http://localhost:8088` but will use `PLAYWRIGHT_BASE_URL` if provided.
|
||||
|
||||
#### Cypress (DEPRECATED - will be removed in Phase 5)
|
||||
|
||||
:::warning
|
||||
Cypress is being phased out in favor of Playwright. Use Playwright for all new tests.
|
||||
:::
|
||||
|
||||
```bash
|
||||
# Set base URL for Cypress
|
||||
CYPRESS_BASE_URL=http://localhost:8081
|
||||
```
|
||||
|
||||
@@ -663,7 +627,7 @@ feature flag to `true`, you can add the following line to the PR body/descriptio
|
||||
FEATURE_TAGGING_SYSTEM=true
|
||||
```
|
||||
|
||||
Similarly, it's possible to disable feature flags with:
|
||||
Simarly, it's possible to disable feature flags with:
|
||||
|
||||
```
|
||||
FEATURE_TAGGING_SYSTEM=false
|
||||
|
||||
@@ -86,6 +86,8 @@ USER root
|
||||
ENV PLAYWRIGHT_BROWSERS_PATH=/usr/local/share/playwright-browsers
|
||||
|
||||
# Install packages using uv into the virtual environment
|
||||
# Superset started using uv after the 4.1 branch; if you are building from apache/superset:4.1.x or an older version,
|
||||
# replace the first two lines with RUN pip install \
|
||||
RUN . /app/.venv/bin/activate && \
|
||||
uv pip install \
|
||||
# install psycopg2 for using PostgreSQL metadata store - could be a MySQL package if using that backend:
|
||||
|
||||
@@ -282,5 +282,5 @@ address.
|
||||
When running `docker compose up`, docker will build what is required behind the scene, but
|
||||
may use the docker cache if assets already exist. Running `docker compose build` prior to
|
||||
`docker compose up` or the equivalent shortcut `docker compose up --build` ensures that your
|
||||
docker images match the definition in the repository. This should only apply to the main
|
||||
docker images matche the definition in the repository. This should only apply to the main
|
||||
docker-compose.yml file (default) and not to the alternative methods defined above.
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"@emotion/core": "^10.0.27",
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@emotion/styled": "^10.0.27",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@saucelabs/theme-github-codeblock": "^0.3.0",
|
||||
"@storybook/addon-docs": "^8.6.11",
|
||||
"@storybook/blocks": "^8.6.11",
|
||||
@@ -50,7 +50,7 @@
|
||||
"@storybook/theming": "^8.6.11",
|
||||
"@superset-ui/core": "^0.20.4",
|
||||
"antd": "^5.26.7",
|
||||
"caniuse-lite": "^1.0.30001739",
|
||||
"caniuse-lite": "^1.0.30001707",
|
||||
"docusaurus-plugin-less": "^2.0.2",
|
||||
"json-bigint": "^1.0.0",
|
||||
"less": "^4.4.0",
|
||||
@@ -65,7 +65,7 @@
|
||||
"storybook": "^8.6.11",
|
||||
"swagger-ui-react": "^5.27.1",
|
||||
"tinycolor2": "^1.4.2",
|
||||
"ts-loader": "^9.5.4"
|
||||
"ts-loader": "^9.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.8.1",
|
||||
@@ -73,14 +73,14 @@
|
||||
"@eslint/js": "^9.32.0",
|
||||
"@types/react": "^19.1.8",
|
||||
"@typescript-eslint/eslint-plugin": "^8.37.0",
|
||||
"@typescript-eslint/parser": "^8.42.0",
|
||||
"eslint": "^9.34.0",
|
||||
"@typescript-eslint/parser": "^8.37.0",
|
||||
"eslint": "^9.32.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.3",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^16.3.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "~5.9.2",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.39.0",
|
||||
"webpack": "^5.101.0"
|
||||
},
|
||||
|
||||
@@ -363,6 +363,110 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
|
||||
]
|
||||
```
|
||||
|
||||
### Keycloak-Specific Configuration using Flask-OIDC
|
||||
|
||||
If you are using Keycloak as OpenID Connect 1.0 Provider, the above configuration based on [`Authlib`](https://authlib.org/) might not work. In this case using [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is a viable option.
|
||||
|
||||
Make sure the pip package [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is installed on the webserver. This was successfully tested using version 2.2.0. This package requires [`Flask-OpenID`](https://pypi.org/project/Flask-OpenID/) as a dependency.
|
||||
|
||||
The following code defines a new security manager. Add it to a new file named `keycloak_security_manager.py`, placed in the same directory as your `superset_config.py` file.
|
||||
|
||||
```python
|
||||
from flask_appbuilder.security.manager import AUTH_OID
|
||||
from superset.security import SupersetSecurityManager
|
||||
from flask_oidc import OpenIDConnect
|
||||
from flask_appbuilder.security.views import AuthOIDView
|
||||
from flask_login import login_user
|
||||
from urllib.parse import quote
|
||||
from flask_appbuilder.views import ModelView, SimpleFormView, expose
|
||||
from flask import (
|
||||
redirect,
|
||||
request
|
||||
)
|
||||
import logging
|
||||
|
||||
class OIDCSecurityManager(SupersetSecurityManager):
|
||||
|
||||
def __init__(self, appbuilder):
|
||||
super(OIDCSecurityManager, self).__init__(appbuilder)
|
||||
if self.auth_type == AUTH_OID:
|
||||
self.oid = OpenIDConnect(self.appbuilder.get_app)
|
||||
self.authoidview = AuthOIDCView
|
||||
|
||||
class AuthOIDCView(AuthOIDView):
|
||||
|
||||
@expose('/login/', methods=['GET', 'POST'])
|
||||
def login(self, flag=True):
|
||||
sm = self.appbuilder.sm
|
||||
oidc = sm.oid
|
||||
|
||||
@self.appbuilder.sm.oid.require_login
|
||||
def handle_login():
|
||||
user = sm.auth_user_oid(oidc.user_getfield('email'))
|
||||
|
||||
if user is None:
|
||||
info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email'])
|
||||
user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'),
|
||||
info.get('email'), sm.find_role('Gamma'))
|
||||
|
||||
login_user(user, remember=False)
|
||||
return redirect(self.appbuilder.get_url_for_index)
|
||||
|
||||
return handle_login()
|
||||
|
||||
@expose('/logout/', methods=['GET', 'POST'])
|
||||
def logout(self):
|
||||
oidc = self.appbuilder.sm.oid
|
||||
|
||||
oidc.logout()
|
||||
super(AuthOIDCView, self).logout()
|
||||
redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login
|
||||
|
||||
return redirect(
|
||||
oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?redirect_uri=' + quote(redirect_url))
|
||||
```
|
||||
|
||||
Then add to your `superset_config.py` file:
|
||||
|
||||
```python
|
||||
from keycloak_security_manager import OIDCSecurityManager
|
||||
from flask_appbuilder.security.manager import AUTH_OID, AUTH_REMOTE_USER, AUTH_DB, AUTH_LDAP, AUTH_OAUTH
|
||||
import os
|
||||
|
||||
AUTH_TYPE = AUTH_OID
|
||||
SECRET_KEY: 'SomethingNotEntirelySecret'
|
||||
OIDC_CLIENT_SECRETS = '/path/to/client_secret.json'
|
||||
OIDC_ID_TOKEN_COOKIE_SECURE = False
|
||||
OIDC_OPENID_REALM: '<myRealm>'
|
||||
OIDC_INTROSPECTION_AUTH_METHOD: 'client_secret_post'
|
||||
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
|
||||
|
||||
# Will allow user self registration, allowing to create Flask users from Authorized User
|
||||
AUTH_USER_REGISTRATION = True
|
||||
|
||||
# The default user self registration role
|
||||
AUTH_USER_REGISTRATION_ROLE = 'Public'
|
||||
```
|
||||
|
||||
Store your client-specific OpenID information in a file called `client_secret.json`. Create this file in the same directory as `superset_config.py`:
|
||||
|
||||
```json
|
||||
{
|
||||
"<myOpenIDProvider>": {
|
||||
"issuer": "https://<myKeycloakDomain>/realms/<myRealm>",
|
||||
"auth_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/auth",
|
||||
"client_id": "https://<myKeycloakDomain>",
|
||||
"client_secret": "<myClientSecret>",
|
||||
"redirect_uris": [
|
||||
"https://<SupersetWebserver>/oauth-authorized/<myOpenIDProvider>"
|
||||
],
|
||||
"userinfo_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/userinfo",
|
||||
"token_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token",
|
||||
"token_introspection_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token/introspect"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## LDAP Authentication
|
||||
|
||||
FAB supports authenticating user credentials against an LDAP server.
|
||||
|
||||
@@ -620,10 +620,10 @@ See [how tos](/docs/contributing/howtos#linting)
|
||||
|
||||
:::tip
|
||||
`act` compatibility of Superset's GHAs is not fully tested. Running `act` locally may or may not
|
||||
work for different actions, and may require fine tuning and local secret-handling.
|
||||
work for different actions, and may require fine tunning and local secret-handling.
|
||||
For those more intricate GHAs that are tricky to run locally, we recommend iterating
|
||||
directly on GHA's infrastructure, by pushing directly on a branch and monitoring GHA logs.
|
||||
For more targeted iteration, see the `gh workflow run --ref {BRANCH}` subcommand of the GitHub CLI.
|
||||
For more targetted iteration, see the `gh workflow run --ref {BRANCH}` subcommand of the GitHub CLI.
|
||||
:::
|
||||
|
||||
For automation and CI/CD, Superset makes extensive use of GitHub Actions (GHA). You
|
||||
|
||||
@@ -232,7 +232,7 @@ CYPRESS_CONFIG=true docker compose up --build
|
||||
```
|
||||
`docker compose` will get to work and expose a Cypress-ready Superset app.
|
||||
This app uses a different database schema (`superset_cypress`) to keep it isolated from
|
||||
your other dev environment(s), a specific set of examples, and a set of configurations that
|
||||
your other dev environmen(s)t, a specific set of examples, and a set of configurations that
|
||||
aligns with the expectations within the end-to-end tests. Also note that it's served on a
|
||||
different port than the default port for the backend (`8088`).
|
||||
|
||||
@@ -627,7 +627,7 @@ feature flag to `true`, you can add the following line to the PR body/descriptio
|
||||
FEATURE_TAGGING_SYSTEM=true
|
||||
```
|
||||
|
||||
Similarly, it's possible to disable feature flags with:
|
||||
Simarly, it's possible to disable feature flags with:
|
||||
|
||||
```
|
||||
FEATURE_TAGGING_SYSTEM=false
|
||||
|
||||
@@ -282,5 +282,5 @@ address.
|
||||
When running `docker compose up`, docker will build what is required behind the scene, but
|
||||
may use the docker cache if assets already exist. Running `docker compose build` prior to
|
||||
`docker compose up` or the equivalent shortcut `docker compose up --build` ensures that your
|
||||
docker images match the definition in the repository. This should only apply to the main
|
||||
docker images matche the definition in the repository. This should only apply to the main
|
||||
docker-compose.yml file (default) and not to the alternative methods defined above.
|
||||
|
||||
122
docs/yarn.lock
122
docs/yarn.lock
@@ -2433,10 +2433,10 @@
|
||||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@eslint/js@9.34.0", "@eslint/js@^9.32.0":
|
||||
version "9.34.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.34.0.tgz#fc423168b9d10e08dea9088d083788ec6442996b"
|
||||
integrity sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==
|
||||
"@eslint/js@9.33.0", "@eslint/js@^9.32.0":
|
||||
version "9.33.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.33.0.tgz#475c92fdddab59b8b8cab960e3de2564a44bf368"
|
||||
integrity sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==
|
||||
|
||||
"@eslint/object-schema@^2.1.6":
|
||||
version "2.1.6"
|
||||
@@ -2598,10 +2598,10 @@
|
||||
unist-util-visit "^5.0.0"
|
||||
vfile "^6.0.0"
|
||||
|
||||
"@mdx-js/react@^3.0.0", "@mdx-js/react@^3.1.1":
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-3.1.1.tgz#24bda7fffceb2fe256f954482123cda1be5f5fef"
|
||||
integrity sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==
|
||||
"@mdx-js/react@^3.0.0", "@mdx-js/react@^3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-3.1.0.tgz#c4522e335b3897b9a845db1dbdd2f966ae8fb0ed"
|
||||
integrity sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==
|
||||
dependencies:
|
||||
"@types/mdx" "^2.0.0"
|
||||
|
||||
@@ -4102,7 +4102,7 @@
|
||||
natural-compare "^1.4.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/parser@8.40.0":
|
||||
"@typescript-eslint/parser@8.40.0", "@typescript-eslint/parser@^8.37.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.40.0.tgz#1bc9f3701ced29540eb76ff2d95ce0d52ddc7e69"
|
||||
integrity sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==
|
||||
@@ -4113,17 +4113,6 @@
|
||||
"@typescript-eslint/visitor-keys" "8.40.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/parser@^8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.42.0.tgz#20ea66f4867981fb5bb62cbe1454250fc4a440ab"
|
||||
integrity sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "8.42.0"
|
||||
"@typescript-eslint/types" "8.42.0"
|
||||
"@typescript-eslint/typescript-estree" "8.42.0"
|
||||
"@typescript-eslint/visitor-keys" "8.42.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/project-service@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.40.0.tgz#1b7ba6079ff580c3215882fe75a43e5d3ed166b9"
|
||||
@@ -4133,15 +4122,6 @@
|
||||
"@typescript-eslint/types" "^8.40.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/project-service@8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.42.0.tgz#636eb3418b6c42c98554dce884943708bf41a583"
|
||||
integrity sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils" "^8.42.0"
|
||||
"@typescript-eslint/types" "^8.42.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.40.0.tgz#2fbfcc8643340d8cd692267e61548b946190be8a"
|
||||
@@ -4150,24 +4130,11 @@
|
||||
"@typescript-eslint/types" "8.40.0"
|
||||
"@typescript-eslint/visitor-keys" "8.40.0"
|
||||
|
||||
"@typescript-eslint/scope-manager@8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.42.0.tgz#36016757bc85b46ea42bae47b61f9421eddedde3"
|
||||
integrity sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.42.0"
|
||||
"@typescript-eslint/visitor-keys" "8.42.0"
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@8.40.0", "@typescript-eslint/tsconfig-utils@^8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz#8e8fdb9b988854aedd04abdde3239c4bdd2d26e4"
|
||||
integrity sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@8.42.0", "@typescript-eslint/tsconfig-utils@^8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.42.0.tgz#21a3e74396fd7443ff930bc41b27789ba7e9236e"
|
||||
integrity sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==
|
||||
|
||||
"@typescript-eslint/type-utils@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.40.0.tgz#a7e4a1f0815dd0ba3e4eef945cc87193ca32c422"
|
||||
@@ -4179,16 +4146,11 @@
|
||||
debug "^4.3.4"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/types@8.40.0":
|
||||
"@typescript-eslint/types@8.40.0", "@typescript-eslint/types@^8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.40.0.tgz#0b580fdf643737aa5c01285314b5c6e9543846a9"
|
||||
integrity sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==
|
||||
|
||||
"@typescript-eslint/types@8.42.0", "@typescript-eslint/types@^8.40.0", "@typescript-eslint/types@^8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.42.0.tgz#ae15c09cebda20473772902033328e87372db008"
|
||||
integrity sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==
|
||||
|
||||
"@typescript-eslint/typescript-estree@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz#295149440ce7da81c790a4e14e327599a3a1e5c9"
|
||||
@@ -4205,22 +4167,6 @@
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/typescript-estree@8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.42.0.tgz#593c3af87d4462252c0d7239d1720b84a1b56864"
|
||||
integrity sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service" "8.42.0"
|
||||
"@typescript-eslint/tsconfig-utils" "8.42.0"
|
||||
"@typescript-eslint/types" "8.42.0"
|
||||
"@typescript-eslint/visitor-keys" "8.42.0"
|
||||
debug "^4.3.4"
|
||||
fast-glob "^3.3.2"
|
||||
is-glob "^4.0.3"
|
||||
minimatch "^9.0.4"
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/utils@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.40.0.tgz#8d0c6430ed2f5dc350784bb0d8be514da1e54054"
|
||||
@@ -4239,14 +4185,6 @@
|
||||
"@typescript-eslint/types" "8.40.0"
|
||||
eslint-visitor-keys "^4.2.1"
|
||||
|
||||
"@typescript-eslint/visitor-keys@8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.42.0.tgz#87c6caaa1ac307bc73a87c1fc469f88f0162f27e"
|
||||
integrity sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.42.0"
|
||||
eslint-visitor-keys "^4.2.1"
|
||||
|
||||
"@ungap/structured-clone@^1.0.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
|
||||
@@ -4766,9 +4704,9 @@ available-typed-arrays@^1.0.7:
|
||||
possible-typed-array-names "^1.0.0"
|
||||
|
||||
axios@^1.9.0:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.12.0.tgz#11248459be05a5ee493485628fa0e4323d0abfc3"
|
||||
integrity sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.11.0.tgz#c2ec219e35e414c025b2095e8b8280278478fdb6"
|
||||
integrity sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.6"
|
||||
form-data "^4.0.4"
|
||||
@@ -5082,10 +5020,10 @@ caniuse-api@^3.0.0:
|
||||
lodash.memoize "^4.1.2"
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001735, caniuse-lite@^1.0.30001739:
|
||||
version "1.0.30001739"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz#b34ce2d56bfc22f4352b2af0144102d623a124f4"
|
||||
integrity sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001707, caniuse-lite@^1.0.30001735:
|
||||
version "1.0.30001735"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz#ba658fd3fd24a4106fd68d5ce472a2c251494dbe"
|
||||
integrity sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==
|
||||
|
||||
ccount@^2.0.0:
|
||||
version "2.0.1"
|
||||
@@ -6775,10 +6713,10 @@ eslint-visitor-keys@^4.2.1:
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1"
|
||||
integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
|
||||
|
||||
eslint@^9.34.0:
|
||||
version "9.34.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.34.0.tgz#0ea1f2c1b5d1671db8f01aa6b8ce722302016f7b"
|
||||
integrity sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==
|
||||
eslint@^9.32.0:
|
||||
version "9.33.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.33.0.tgz#cc186b3d9eb0e914539953d6a178a5b413997b73"
|
||||
integrity sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.2.0"
|
||||
"@eslint-community/regexpp" "^4.12.1"
|
||||
@@ -6786,7 +6724,7 @@ eslint@^9.34.0:
|
||||
"@eslint/config-helpers" "^0.3.1"
|
||||
"@eslint/core" "^0.15.2"
|
||||
"@eslint/eslintrc" "^3.3.1"
|
||||
"@eslint/js" "9.34.0"
|
||||
"@eslint/js" "9.33.0"
|
||||
"@eslint/plugin-kit" "^0.3.5"
|
||||
"@humanfs/node" "^0.16.6"
|
||||
"@humanwhocodes/module-importer" "^1.0.1"
|
||||
@@ -13201,10 +13139,10 @@ ts-dedent@^2.0.0, ts-dedent@^2.2.0:
|
||||
resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
|
||||
integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==
|
||||
|
||||
ts-loader@^9.5.4:
|
||||
version "9.5.4"
|
||||
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.4.tgz#44b571165c10fb5a90744aa5b7e119233c4f4585"
|
||||
integrity sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==
|
||||
ts-loader@^9.5.2:
|
||||
version "9.5.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.2.tgz#1f3d7f4bb709b487aaa260e8f19b301635d08020"
|
||||
integrity sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==
|
||||
dependencies:
|
||||
chalk "^4.1.0"
|
||||
enhanced-resolve "^5.0.0"
|
||||
@@ -13331,10 +13269,10 @@ typescript-eslint@^8.39.0:
|
||||
"@typescript-eslint/typescript-estree" "8.40.0"
|
||||
"@typescript-eslint/utils" "8.40.0"
|
||||
|
||||
typescript@~5.9.2:
|
||||
version "5.9.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6"
|
||||
integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==
|
||||
typescript@~5.8.3:
|
||||
version "5.8.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
|
||||
integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==
|
||||
|
||||
ufo@^1.5.4:
|
||||
version "1.6.1"
|
||||
|
||||
@@ -35,7 +35,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.12",
|
||||
]
|
||||
dependencies = [
|
||||
# no bounds for apache-superset-core until we have a stable version
|
||||
# no bounds for apache-superset-core until a stable version
|
||||
"apache-superset-core",
|
||||
"backoff>=1.8.0",
|
||||
"celery>=5.3.6, <6.0.0",
|
||||
@@ -48,7 +48,7 @@ dependencies = [
|
||||
"cryptography>=42.0.4, <45.0.0",
|
||||
"deprecation>=2.1.0, <2.2.0",
|
||||
"flask>=2.2.5, <3.0.0",
|
||||
"flask-appbuilder>=5.0.0,<6",
|
||||
"flask-appbuilder>=4.8.1, <5.0.0",
|
||||
"flask-caching>=2.1.0, <3",
|
||||
"flask-compress>=1.13, <2.0",
|
||||
"flask-talisman>=1.0.0, <2.0",
|
||||
@@ -76,7 +76,7 @@ dependencies = [
|
||||
"packaging",
|
||||
# --------------------------
|
||||
# pandas and related (wanting pandas[performance] without numba as it's 100+MB and not needed)
|
||||
"pandas[excel]>=2.0.3, <2.2",
|
||||
"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
|
||||
# --------------------------
|
||||
"parsedatetime",
|
||||
@@ -84,12 +84,11 @@ dependencies = [
|
||||
"pgsanity",
|
||||
"Pillow>=11.0.0, <12",
|
||||
"polyline>=2.0.0, <3.0",
|
||||
"pydantic>=2.8.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-geohash",
|
||||
"pyarrow>=16.1.0, <19", # before upgrading pyarrow, check that all db dependencies support this, see e.g. https://github.com/apache/superset/pull/34693
|
||||
"pyarrow>=16.1.0, <17", # before upgrading pyarrow, check that all db dependencies support this, see e.g. https://github.com/apache/superset/pull/34693
|
||||
"pyyaml>=6.0.0, <7.0.0",
|
||||
"PyJWT>=2.4.0, <3.0",
|
||||
"redis>=4.6.0, <5.0",
|
||||
@@ -125,8 +124,8 @@ cockroachdb = ["cockroachdb>=0.3.5, <0.4"]
|
||||
crate = ["sqlalchemy-cratedb>=0.40.1, <1"]
|
||||
databend = ["databend-sqlalchemy>=0.3.2, <1.0"]
|
||||
databricks = [
|
||||
"databricks-sql-connector==4.1.2",
|
||||
"databricks-sqlalchemy==1.0.5",
|
||||
"databricks-sql-connector>=2.0.2, <3",
|
||||
"sqlalchemy-databricks>=0.2.0",
|
||||
]
|
||||
db2 = ["ibm-db-sa>0.3.8, <=0.4.0"]
|
||||
denodo = ["denodo-sqlalchemy~=1.0.6"]
|
||||
@@ -314,7 +313,6 @@ select = [
|
||||
"E",
|
||||
"F",
|
||||
"F",
|
||||
"G",
|
||||
"I",
|
||||
"N",
|
||||
"PT",
|
||||
@@ -330,7 +328,6 @@ ignore = [
|
||||
"PT006",
|
||||
"T201",
|
||||
"N999",
|
||||
"G201",
|
||||
]
|
||||
extend-select = ["I"]
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ alembic==1.15.2
|
||||
# via flask-migrate
|
||||
amqp==5.3.1
|
||||
# via kombu
|
||||
annotated-types==0.7.0
|
||||
# via pydantic
|
||||
apispec==6.6.1
|
||||
# via
|
||||
# -r requirements/base.in
|
||||
@@ -116,11 +114,11 @@ flask==2.3.3
|
||||
# flask-session
|
||||
# flask-sqlalchemy
|
||||
# flask-wtf
|
||||
flask-appbuilder==5.0.0
|
||||
flask-appbuilder==4.8.1
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# apache-superset-core
|
||||
flask-babel==3.1.0
|
||||
flask-babel==2.0.0
|
||||
# via flask-appbuilder
|
||||
flask-caching==2.3.1
|
||||
# via apache-superset (pyproject.toml)
|
||||
@@ -267,7 +265,7 @@ packaging==25.0
|
||||
# limits
|
||||
# marshmallow
|
||||
# shillelagh
|
||||
pandas==2.1.4
|
||||
pandas==2.0.3
|
||||
# via apache-superset (pyproject.toml)
|
||||
paramiko==3.5.1
|
||||
# via
|
||||
@@ -299,10 +297,6 @@ pyasn1-modules==0.4.2
|
||||
# via google-auth
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
pydantic==2.11.7
|
||||
# via apache-superset (pyproject.toml)
|
||||
pydantic-core==2.33.2
|
||||
# via pydantic
|
||||
pygments==2.19.1
|
||||
# via rich
|
||||
pyjwt==2.10.1
|
||||
@@ -413,15 +407,10 @@ typing-extensions==4.14.0
|
||||
# alembic
|
||||
# cattrs
|
||||
# limits
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# pyopenssl
|
||||
# referencing
|
||||
# selenium
|
||||
# shillelagh
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.1
|
||||
# via pydantic
|
||||
tzdata==2025.2
|
||||
# via
|
||||
# kombu
|
||||
|
||||
@@ -18,10 +18,6 @@ amqp==5.3.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# kombu
|
||||
annotated-types==0.7.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# pydantic
|
||||
apispec==6.6.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -212,12 +208,12 @@ flask==2.3.3
|
||||
# flask-sqlalchemy
|
||||
# flask-testing
|
||||
# flask-wtf
|
||||
flask-appbuilder==5.0.0
|
||||
flask-appbuilder==4.8.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
# apache-superset-core
|
||||
flask-babel==3.1.0
|
||||
flask-babel==2.0.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask-appbuilder
|
||||
@@ -456,7 +452,7 @@ marshmallow-sqlalchemy==1.4.0
|
||||
# flask-appbuilder
|
||||
marshmallow-union==0.1.15
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# -c requirements/base.txt
|
||||
# apache-superset
|
||||
matplotlib==3.9.0
|
||||
# via prophet
|
||||
@@ -537,7 +533,7 @@ packaging==25.0
|
||||
# pytest
|
||||
# shillelagh
|
||||
# sqlalchemy-bigquery
|
||||
pandas==2.1.4
|
||||
pandas==2.0.3
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -637,14 +633,6 @@ pycparser==2.22
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# cffi
|
||||
pydantic==2.11.7
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
pydantic-core==2.33.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# pydantic
|
||||
pydata-google-auth==1.9.0
|
||||
# via pandas-gbq
|
||||
pydruid==0.6.9
|
||||
@@ -888,17 +876,10 @@ typing-extensions==4.14.0
|
||||
# apache-superset
|
||||
# cattrs
|
||||
# limits
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# pyopenssl
|
||||
# referencing
|
||||
# selenium
|
||||
# shillelagh
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# pydantic
|
||||
tzdata==2025.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
[project]
|
||||
name = "apache-superset-core"
|
||||
version = "0.0.1rc2"
|
||||
version = "0.0.1rc1"
|
||||
description = "Core Python package for building Apache Superset backend extensions and integrations"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
@@ -42,7 +42,7 @@ classifiers = [
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
]
|
||||
dependencies = [
|
||||
"flask-appbuilder>=5.0.0,<6",
|
||||
"flask-appbuilder>=4.5.3, <5.0.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -57,7 +57,5 @@ requires = ["setuptools>=76.0.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["superset_core"]
|
||||
package-dir = { "" = "src" }
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
||||
426
superset-embedded-sdk/package-lock.json
generated
426
superset-embedded-sdk/package-lock.json
generated
@@ -19,6 +19,7 @@
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^22.5.4",
|
||||
"axios": "^1.7.7",
|
||||
"babel-loader": "^9.1.3",
|
||||
"jest": "^29.7.0",
|
||||
"tscw-config": "^1.1.2",
|
||||
@@ -3232,6 +3233,24 @@
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
|
||||
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||
@@ -3625,6 +3644,19 @@
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -3811,6 +3843,18 @@
|
||||
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
|
||||
@@ -4001,6 +4045,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-newline": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
||||
@@ -4010,6 +4063,20 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.19",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz",
|
||||
@@ -4070,12 +4137,57 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
|
||||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
@@ -4516,6 +4628,42 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-readdir-recursive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
|
||||
@@ -4569,6 +4717,30 @@
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-package-type": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
|
||||
@@ -4578,6 +4750,19 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
@@ -4640,6 +4825,18 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -4668,6 +4865,45 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
@@ -6856,6 +7092,15 @@
|
||||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
@@ -7169,6 +7414,12 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
@@ -10473,6 +10724,23 @@
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
|
||||
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"babel-jest": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||
@@ -10755,6 +11023,16 @@
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -10880,6 +11158,15 @@
|
||||
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
|
||||
"dev": true
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
|
||||
@@ -11012,12 +11299,29 @@
|
||||
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||
"dev": true
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||
"dev": true
|
||||
},
|
||||
"detect-newline": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
||||
"integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
|
||||
"dev": true
|
||||
},
|
||||
"dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.5.19",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz",
|
||||
@@ -11061,12 +11365,45 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true
|
||||
},
|
||||
"es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true
|
||||
},
|
||||
"es-module-lexer": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
|
||||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
|
||||
"dev": true
|
||||
},
|
||||
"es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
@@ -11375,6 +11712,25 @@
|
||||
"path-exists": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"dev": true
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"fs-readdir-recursive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
|
||||
@@ -11412,12 +11768,40 @@
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"get-package-type": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
|
||||
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
@@ -11460,6 +11844,12 @@
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||
"dev": true
|
||||
},
|
||||
"gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -11481,6 +11871,30 @@
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-symbols": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
@@ -13091,6 +13505,12 @@
|
||||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true
|
||||
},
|
||||
"merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
@@ -13322,6 +13742,12 @@
|
||||
"sisteransi": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
},
|
||||
"punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^22.5.4",
|
||||
"axios": "^1.7.7",
|
||||
"babel-loader": "^9.1.3",
|
||||
"jest": "^29.7.0",
|
||||
"tscw-config": "^1.1.2",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const axios = require('axios');
|
||||
const { name, version } = require('./package.json');
|
||||
|
||||
function log(...args) {
|
||||
@@ -35,7 +36,9 @@ function logError(...args) {
|
||||
// npm commands output a bunch of garbage in the edge cases,
|
||||
// and require sending semi-validated strings to the command line,
|
||||
// so let's just use good old http.
|
||||
const { status } = await fetch(packageUrl);
|
||||
const { status } = await axios.get(packageUrl, {
|
||||
validateStatus: (status) => true // we literally just want the status so any status is valid
|
||||
});
|
||||
|
||||
if (status === 200) {
|
||||
log('version already exists on npm, exiting');
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
[project]
|
||||
name = "apache-superset-extensions-cli"
|
||||
version = "0.0.1rc2"
|
||||
version = "0.0.1rc1"
|
||||
description = "Official command-line interface for building, bundling, and managing Apache Superset extensions"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
@@ -43,8 +43,7 @@ classifiers = [
|
||||
"Topic :: System :: Software Distribution",
|
||||
]
|
||||
dependencies = [
|
||||
# no bounds for apache-superset-core until we have a stable version
|
||||
"apache-superset-core",
|
||||
"apache-superset-core>=0.0.1rc1, <0.2",
|
||||
"click>=8.0.3",
|
||||
"jinja2>=3.1.6",
|
||||
"semver>=3.0.4",
|
||||
@@ -71,14 +70,8 @@ requires = ["setuptools>=76.0.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["superset_extensions_cli"]
|
||||
package-dir = { "" = "src" }
|
||||
include-package-data = true
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
superset_extensions_cli = ["templates/**/*"]
|
||||
|
||||
[project.scripts]
|
||||
superset-extensions = "superset_extensions_cli.cli:app"
|
||||
|
||||
@@ -160,9 +160,7 @@ def test_validate_npm_handles_file_not_found_exception(mock_run, mock_which):
|
||||
def test_validate_npm_does_not_catch_other_subprocess_exceptions(
|
||||
mock_run, mock_which, exception_type
|
||||
):
|
||||
"""
|
||||
Test validate_npm does not catch OSError and PermissionError (they propagate up).
|
||||
"""
|
||||
"""Test validate_npm does not catch OSError and PermissionError (they propagate up)."""
|
||||
mock_which.return_value = "/usr/bin/npm"
|
||||
mock_run.side_effect = exception_type("Test error")
|
||||
|
||||
|
||||
@@ -32,3 +32,6 @@ esm/
|
||||
lib/
|
||||
tmp/
|
||||
storybook-static/
|
||||
|
||||
# Orval generated code
|
||||
packages/superset-ui-core/src/orval-generated-code/
|
||||
|
||||
@@ -323,7 +323,6 @@ module.exports = {
|
||||
'*.stories.tsx',
|
||||
'*.stories.jsx',
|
||||
'fixtures.*',
|
||||
'playwright/**/*',
|
||||
],
|
||||
excludedFiles: 'cypress-base/cypress/**/*',
|
||||
plugins: ['jest', 'jest-dom', 'no-only-tests', 'testing-library'],
|
||||
@@ -398,13 +397,6 @@ module.exports = {
|
||||
'react/no-void-elements': 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['playwright/**/*'],
|
||||
rules: {
|
||||
'import/no-unresolved': 0, // Playwright is not installed in main build
|
||||
'import/no-extraneous-dependencies': 0, // Playwright is not installed in main build
|
||||
},
|
||||
},
|
||||
],
|
||||
// eslint-disable-next-line no-dupe-keys
|
||||
rules: {
|
||||
|
||||
4
superset-frontend/.gitignore
vendored
4
superset-frontend/.gitignore
vendored
@@ -3,3 +3,7 @@ cypress/screenshots
|
||||
cypress/videos
|
||||
src/temp
|
||||
.temp_cache/
|
||||
|
||||
# Orval OpenAPI generation artifacts
|
||||
openapi-raw.json
|
||||
openapi-normalized.json
|
||||
|
||||
@@ -175,7 +175,7 @@ describe('Charts list', () => {
|
||||
interceptDelete();
|
||||
cy.getBySel('sort-header').contains('Name').click();
|
||||
|
||||
// Modal closes immediately without this
|
||||
// Modal closes immediatly without this
|
||||
cy.wait(2000);
|
||||
|
||||
cy.getBySel('table-row').eq(0).contains('3 - Sample chart');
|
||||
|
||||
@@ -0,0 +1,766 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { Interception } from 'cypress/types/net-stubbing';
|
||||
import { waitForChartLoad } from 'cypress/utils';
|
||||
import { SUPPORTED_CHARTS_DASHBOARD } from 'cypress/utils/urls';
|
||||
import {
|
||||
openTopLevelTab,
|
||||
SUPPORTED_TIER1_CHARTS,
|
||||
SUPPORTED_TIER2_CHARTS,
|
||||
} from './utils';
|
||||
import {
|
||||
interceptExploreJson,
|
||||
interceptV1ChartData,
|
||||
interceptFormDataKey,
|
||||
} from '../explore/utils';
|
||||
|
||||
const interceptDrillInfo = () => {
|
||||
cy.intercept('GET', '**/api/v1/dataset/*/drill_info/*', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
result: {
|
||||
id: 1,
|
||||
changed_on_humanized: '2 days ago',
|
||||
created_on_humanized: 'a week ago',
|
||||
table_name: 'birth_names',
|
||||
changed_by: {
|
||||
first_name: 'Admin',
|
||||
last_name: 'User',
|
||||
},
|
||||
created_by: {
|
||||
first_name: 'Admin',
|
||||
last_name: 'User',
|
||||
},
|
||||
owners: [
|
||||
{
|
||||
first_name: 'Admin',
|
||||
last_name: 'User',
|
||||
},
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
column_name: 'gender',
|
||||
verbose_name: null,
|
||||
},
|
||||
{
|
||||
column_name: 'state',
|
||||
verbose_name: null,
|
||||
},
|
||||
{
|
||||
column_name: 'name',
|
||||
verbose_name: null,
|
||||
},
|
||||
{
|
||||
column_name: 'ds',
|
||||
verbose_name: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}).as('drillInfo');
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
cy.get('body').then($body => {
|
||||
if ($body.find('[data-test="close-drill-by-modal"]').length) {
|
||||
cy.getBySel('close-drill-by-modal').click({ force: true });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const openTableContextMenu = (
|
||||
cellContent: string,
|
||||
tableSelector = "[data-test-viz-type='table']",
|
||||
) => {
|
||||
cy.get(tableSelector).scrollIntoView();
|
||||
cy.get(tableSelector).contains(cellContent).first().rightclick();
|
||||
};
|
||||
|
||||
const drillBy = (targetDrillByColumn: string, isLegacy = false) => {
|
||||
if (isLegacy) {
|
||||
interceptExploreJson('legacyData');
|
||||
} else {
|
||||
interceptV1ChartData();
|
||||
}
|
||||
|
||||
cy.get('.ant-dropdown:not(.ant-dropdown-hidden)', { timeout: 15000 })
|
||||
.should('be.visible')
|
||||
.find("[role='menu'] [role='menuitem']")
|
||||
.contains(/^Drill by$/)
|
||||
.trigger('mouseover', { force: true });
|
||||
|
||||
cy.get(
|
||||
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
|
||||
{ timeout: 15000 },
|
||||
)
|
||||
.should('be.visible')
|
||||
.find('[role="menuitem"]')
|
||||
.contains(new RegExp(`^${targetDrillByColumn}$`))
|
||||
.click();
|
||||
|
||||
cy.get(
|
||||
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
|
||||
).trigger('mouseout', { clientX: 0, clientY: 0, force: true });
|
||||
|
||||
cy.get(
|
||||
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
|
||||
).should('not.exist');
|
||||
|
||||
if (isLegacy) {
|
||||
return cy.wait('@legacyData');
|
||||
}
|
||||
return cy.wait('@v1Data');
|
||||
};
|
||||
|
||||
const verifyExpectedFormData = (
|
||||
interceptedRequest: Interception,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
expectedFormData: Record<string, any>,
|
||||
) => {
|
||||
const actualFormData = interceptedRequest.request.body?.form_data;
|
||||
Object.entries(expectedFormData).forEach(([key, val]) => {
|
||||
expect(actualFormData?.[key]).to.eql(val);
|
||||
});
|
||||
};
|
||||
|
||||
const testEchart = (
|
||||
vizType: string,
|
||||
chartName: string,
|
||||
drillClickCoordinates: [[number, number], [number, number]],
|
||||
furtherDrillDimension = 'name',
|
||||
) => {
|
||||
cy.get(`[data-test-viz-type='${vizType}'] canvas`).then($canvas => {
|
||||
// click 'boy'
|
||||
cy.wrap($canvas).scrollIntoView();
|
||||
cy.wrap($canvas).trigger(
|
||||
'mouseover',
|
||||
drillClickCoordinates[0][0],
|
||||
drillClickCoordinates[0][1],
|
||||
);
|
||||
cy.wrap($canvas).rightclick(
|
||||
drillClickCoordinates[0][0],
|
||||
drillClickCoordinates[0][1],
|
||||
);
|
||||
|
||||
drillBy('state').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupby: ['state'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.getBySel(`"Drill by: ${chartName}-modal"`).as('drillByModal');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.draggable-trigger')
|
||||
.should('contain', chartName);
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'state');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible');
|
||||
|
||||
// further drill
|
||||
cy.get(`[data-test="drill-by-chart"] canvas`).then($canvas => {
|
||||
// click 'other'
|
||||
cy.wrap($canvas).scrollIntoView();
|
||||
cy.wrap($canvas).trigger(
|
||||
'mouseover',
|
||||
drillClickCoordinates[1][0],
|
||||
drillClickCoordinates[1][1],
|
||||
);
|
||||
cy.wrap($canvas).rightclick(
|
||||
drillClickCoordinates[1][0],
|
||||
drillClickCoordinates[1][1],
|
||||
);
|
||||
|
||||
drillBy(furtherDrillDimension).then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupby: [furtherDrillDimension],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'other',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'state',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible');
|
||||
|
||||
// undo - back to drill by state
|
||||
interceptV1ChartData('drillByUndo');
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'state (other)')
|
||||
.and('contain', furtherDrillDimension)
|
||||
.contains('state (other)')
|
||||
.click();
|
||||
cy.wait('@drillByUndo').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupby: ['state'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('not.contain', 'state (other)')
|
||||
.and('not.contain', furtherDrillDimension)
|
||||
.and('contain', 'state');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('Drill by modal', () => {
|
||||
beforeEach(() => {
|
||||
closeModal();
|
||||
});
|
||||
before(() => {
|
||||
interceptDrillInfo();
|
||||
cy.visit(SUPPORTED_CHARTS_DASHBOARD);
|
||||
});
|
||||
|
||||
describe('Modal actions + Table', () => {
|
||||
before(() => {
|
||||
closeModal();
|
||||
interceptDrillInfo();
|
||||
openTopLevelTab('Tier 1');
|
||||
SUPPORTED_TIER1_CHARTS.forEach(waitForChartLoad);
|
||||
});
|
||||
|
||||
it.only('opens the modal from the context menu', () => {
|
||||
openTableContextMenu('boy');
|
||||
drillBy('state').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupby: ['state'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.getBySel('"Drill by: Table-modal"').as('drillByModal');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.draggable-trigger')
|
||||
.should('contain', 'Drill by: Table');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="metadata-bar"]')
|
||||
.should('be.visible');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'state');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible')
|
||||
.and('contain', 'state')
|
||||
.and('contain', 'sum__num');
|
||||
|
||||
// further drilling
|
||||
openTableContextMenu('CA', '[data-test="drill-by-chart"]');
|
||||
drillBy('name').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupby: ['name'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'CA',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'state',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible')
|
||||
.and('not.contain', 'state')
|
||||
.and('contain', 'name')
|
||||
.and('contain', 'sum__num');
|
||||
|
||||
// undo - back to drill by state
|
||||
interceptV1ChartData('drillByUndo');
|
||||
interceptFormDataKey();
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'state (CA)')
|
||||
.and('contain', 'name')
|
||||
.contains('state (CA)')
|
||||
.click();
|
||||
cy.wait('@drillByUndo').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupby: ['state'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible')
|
||||
.and('not.contain', 'name')
|
||||
.and('contain', 'state')
|
||||
.and('contain', 'sum__num');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('not.contain', 'state (CA)')
|
||||
.and('not.contain', 'name')
|
||||
.and('contain', 'state');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-display-toggle"]')
|
||||
.contains('Table')
|
||||
.click();
|
||||
|
||||
cy.getBySel('drill-by-chart').should('not.exist');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-results-table"]')
|
||||
.should('be.visible');
|
||||
|
||||
cy.wait('@formDataKey').then(intercept => {
|
||||
cy.get('@drillByModal')
|
||||
.contains('Edit chart')
|
||||
.should('have.attr', 'href')
|
||||
.and(
|
||||
'contain',
|
||||
`/explore/?form_data_key=${intercept.response?.body?.key}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tier 1 charts', () => {
|
||||
before(() => {
|
||||
closeModal();
|
||||
interceptDrillInfo();
|
||||
openTopLevelTab('Tier 1');
|
||||
SUPPORTED_TIER1_CHARTS.forEach(waitForChartLoad);
|
||||
});
|
||||
|
||||
it('Pivot Table', () => {
|
||||
openTableContextMenu('boy', "[data-test-viz-type='pivot_table_v2']");
|
||||
drillBy('name').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupbyRows: ['state'],
|
||||
groupbyColumns: ['name'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.getBySel('"Drill by: Pivot Table-modal"').as('drillByModal');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.draggable-trigger')
|
||||
.should('contain', 'Drill by: Pivot Table');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'name');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible')
|
||||
.and('contain', 'state')
|
||||
.and('contain', 'name')
|
||||
.and('contain', 'sum__num')
|
||||
.and('not.contain', 'Gender');
|
||||
|
||||
openTableContextMenu('CA', '[data-test="drill-by-chart"]');
|
||||
drillBy('ds').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupbyColumns: ['name'],
|
||||
groupbyRows: ['ds'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'CA',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'state',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible')
|
||||
.and('contain', 'name')
|
||||
.and('contain', 'ds')
|
||||
.and('contain', 'sum__num')
|
||||
.and('not.contain', 'state');
|
||||
|
||||
interceptV1ChartData('drillByUndo');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'name (CA)')
|
||||
.and('contain', 'ds')
|
||||
.contains('name (CA)')
|
||||
.click();
|
||||
cy.wait('@drillByUndo').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupbyRows: ['state'],
|
||||
groupbyColumns: ['name'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible')
|
||||
.and('not.contain', 'ds')
|
||||
.and('contain', 'state')
|
||||
.and('contain', 'name')
|
||||
.and('contain', 'sum__num');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('not.contain', 'name (CA)')
|
||||
.and('not.contain', 'ds')
|
||||
.and('contain', 'name');
|
||||
});
|
||||
|
||||
it('Line chart', () => {
|
||||
testEchart('echarts_timeseries_line', 'Line Chart', [
|
||||
[85, 93],
|
||||
[85, 93],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Area Chart', () => {
|
||||
testEchart('echarts_area', 'Area Chart', [
|
||||
[85, 93],
|
||||
[85, 93],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Scatter Chart', () => {
|
||||
testEchart('echarts_timeseries_scatter', 'Scatter Chart', [
|
||||
[85, 93],
|
||||
[85, 93],
|
||||
]);
|
||||
});
|
||||
|
||||
it.skip('Bar Chart', () => {
|
||||
testEchart('echarts_timeseries_bar', 'Bar Chart', [
|
||||
[85, 94],
|
||||
[490, 68],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Pie Chart', () => {
|
||||
testEchart('pie', 'Pie Chart', [
|
||||
[243, 167],
|
||||
[534, 248],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tier 2 charts', () => {
|
||||
before(() => {
|
||||
closeModal();
|
||||
interceptDrillInfo();
|
||||
openTopLevelTab('Tier 2');
|
||||
SUPPORTED_TIER2_CHARTS.forEach(waitForChartLoad);
|
||||
});
|
||||
|
||||
it('Box Plot Chart', () => {
|
||||
testEchart(
|
||||
'box_plot',
|
||||
'Box Plot Chart',
|
||||
[
|
||||
[139, 277],
|
||||
[787, 441],
|
||||
],
|
||||
'ds',
|
||||
);
|
||||
});
|
||||
|
||||
it('Generic Chart', () => {
|
||||
testEchart('echarts_timeseries', 'Generic Chart', [
|
||||
[85, 93],
|
||||
[85, 93],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Smooth Line Chart', () => {
|
||||
testEchart('echarts_timeseries_smooth', 'Smooth Line Chart', [
|
||||
[85, 93],
|
||||
[85, 93],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Step Line Chart', () => {
|
||||
testEchart('echarts_timeseries_step', 'Step Line Chart', [
|
||||
[85, 93],
|
||||
[85, 93],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Funnel Chart', () => {
|
||||
testEchart('funnel', 'Funnel Chart', [
|
||||
[154, 80],
|
||||
[421, 39],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Gauge Chart', () => {
|
||||
testEchart('gauge_chart', 'Gauge Chart', [
|
||||
[151, 95],
|
||||
[300, 143],
|
||||
]);
|
||||
});
|
||||
|
||||
it.skip('Radar Chart', () => {
|
||||
testEchart('radar', 'Radar Chart', [
|
||||
[182, 49],
|
||||
[423, 91],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Treemap V2 Chart', () => {
|
||||
testEchart('treemap_v2', 'Treemap V2 Chart', [
|
||||
[145, 84],
|
||||
[220, 105],
|
||||
]);
|
||||
});
|
||||
|
||||
it.skip('Mixed Chart', () => {
|
||||
cy.get('[data-test-viz-type="mixed_timeseries"] canvas').then($canvas => {
|
||||
// click 'boy'
|
||||
cy.wrap($canvas).scrollIntoView();
|
||||
cy.wrap($canvas).trigger('mouseover', 85, 93);
|
||||
cy.wrap($canvas).rightclick(85, 93);
|
||||
|
||||
drillBy('name').then(intercepted => {
|
||||
const { queries } = intercepted.request.body;
|
||||
expect(queries[0].columns).to.eql(['name']);
|
||||
expect(queries[0].filters).to.eql([
|
||||
{ col: 'gender', op: '==', val: 'boy' },
|
||||
]);
|
||||
expect(queries[1].columns).to.eql(['state']);
|
||||
expect(queries[1].filters).to.eql([]);
|
||||
});
|
||||
|
||||
cy.getBySel('"Drill by: Mixed Chart-modal"').as('drillByModal');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.draggable-trigger')
|
||||
.should('contain', 'Mixed Chart');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'name');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible');
|
||||
|
||||
// further drill
|
||||
cy.get(`[data-test="drill-by-chart"] canvas`).then($canvas => {
|
||||
// click second query
|
||||
cy.wrap($canvas).scrollIntoView();
|
||||
cy.wrap($canvas).trigger('mouseover', 261, 114);
|
||||
cy.wrap($canvas).rightclick(261, 114);
|
||||
|
||||
drillBy('ds').then(intercepted => {
|
||||
const { queries } = intercepted.request.body;
|
||||
expect(queries[0].columns).to.eql(['name']);
|
||||
expect(queries[0].filters).to.eql([
|
||||
{ col: 'gender', op: '==', val: 'boy' },
|
||||
]);
|
||||
expect(queries[1].columns).to.eql(['ds']);
|
||||
expect(queries[1].filters).to.eql([
|
||||
{ col: 'state', op: '==', val: 'other' },
|
||||
]);
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible');
|
||||
|
||||
// undo - back to drill by state
|
||||
interceptV1ChartData('drillByUndo');
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'name (other)')
|
||||
.and('contain', 'ds')
|
||||
.contains('name (other)')
|
||||
.click();
|
||||
|
||||
cy.wait('@drillByUndo').then(intercepted => {
|
||||
const { queries } = intercepted.request.body;
|
||||
expect(queries[0].columns).to.eql(['name']);
|
||||
expect(queries[0].filters).to.eql([
|
||||
{ col: 'gender', op: '==', val: 'boy' },
|
||||
]);
|
||||
expect(queries[1].columns).to.eql(['state']);
|
||||
expect(queries[1].filters).to.eql([]);
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('not.contain', 'name (other)')
|
||||
.and('not.contain', 'ds')
|
||||
.and('contain', 'name');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// ***********************************************
|
||||
// Tests for setting controls in the UI
|
||||
// ***********************************************
|
||||
import { interceptChart, setSelectSearchInput } from 'cypress/utils';
|
||||
|
||||
describe('Datasource control', () => {
|
||||
const newMetricName = `abc${Date.now()}`;
|
||||
|
||||
it('should allow edit dataset', () => {
|
||||
interceptChart({ legacy: false }).as('chartData');
|
||||
|
||||
cy.visitChartByName('Num Births Trend');
|
||||
cy.verifySliceSuccess({ waitAlias: '@chartData' });
|
||||
|
||||
cy.get('[data-test="datasource-menu-trigger"]').click();
|
||||
|
||||
cy.get('[data-test="edit-dataset"]').click();
|
||||
|
||||
cy.get('[data-test="edit-dataset-tabs"]').within(() => {
|
||||
cy.contains('Metrics').click();
|
||||
});
|
||||
// create new metric
|
||||
cy.get('[data-test="crud-add-table-item"]', { timeout: 10000 }).click();
|
||||
cy.wait(1000);
|
||||
cy.get('.ant-table-body [data-test="textarea-editable-title-input"]')
|
||||
.first()
|
||||
.click();
|
||||
|
||||
cy.get('.ant-table-body [data-test="textarea-editable-title-input"]')
|
||||
.first()
|
||||
.focus();
|
||||
cy.focused().clear({ force: true });
|
||||
cy.focused().type(`${newMetricName}{enter}`, { force: true });
|
||||
|
||||
cy.get('[data-test="datasource-modal-save"]').click();
|
||||
cy.get('.ant-modal-confirm-btns button').contains('OK').click();
|
||||
// select new metric
|
||||
cy.get('[data-test=metrics]')
|
||||
.contains('Drop columns/metrics here or click')
|
||||
.click();
|
||||
|
||||
cy.get('input[aria-label="Select saved metrics"]')
|
||||
.should('exist')
|
||||
.then($input => {
|
||||
setSelectSearchInput($input, newMetricName);
|
||||
});
|
||||
|
||||
// delete metric
|
||||
cy.get('[data-test="datasource-menu-trigger"]').click();
|
||||
cy.get('[data-test="edit-dataset"]').click();
|
||||
cy.get('.ant-modal-content').within(() => {
|
||||
cy.get('[data-test="collection-tab-Metrics"]')
|
||||
.contains('Metrics')
|
||||
.click();
|
||||
});
|
||||
cy.get(`[data-test="textarea-editable-title-input"]`)
|
||||
.contains(newMetricName)
|
||||
.closest('tr')
|
||||
.find('[data-test="crud-delete-icon"]')
|
||||
.click();
|
||||
cy.get('[data-test="datasource-modal-save"]').click();
|
||||
cy.get('.ant-modal-confirm-btns button').contains('OK').click();
|
||||
cy.get('[data-test="metrics"]').contains(newMetricName).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Color scheme control', () => {
|
||||
beforeEach(() => {
|
||||
interceptChart({ legacy: false }).as('chartData');
|
||||
|
||||
cy.visitChartByName('Num Births Trend');
|
||||
cy.verifySliceSuccess({ waitAlias: '@chartData' });
|
||||
});
|
||||
|
||||
it('should show color options with and without tooltips', () => {
|
||||
cy.get('#controlSections-tab-CUSTOMIZE').click();
|
||||
cy.get('.ant-select-selection-item .color-scheme-label').contains(
|
||||
'Superset Colors',
|
||||
);
|
||||
cy.get('.ant-select-selection-item .color-scheme-label').trigger(
|
||||
'mouseover',
|
||||
);
|
||||
cy.get('.color-scheme-tooltip').should('be.visible');
|
||||
cy.get('.color-scheme-tooltip').contains('Superset Colors');
|
||||
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
|
||||
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
|
||||
|
||||
cy.get('.color-scheme-label')
|
||||
.contains('Superset Colors')
|
||||
.trigger('mouseover');
|
||||
|
||||
cy.get('.color-scheme-label')
|
||||
.contains('Superset Colors')
|
||||
.trigger('mouseout');
|
||||
|
||||
cy.focused().type('lyftColors');
|
||||
cy.getBySel('lyftColors').should('exist');
|
||||
cy.getBySel('lyftColors').trigger('mouseover', { force: true });
|
||||
cy.get('.color-scheme-tooltip').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
describe('VizType control', () => {
|
||||
beforeEach(() => {
|
||||
interceptChart({ legacy: false }).as('tableChartData');
|
||||
interceptChart({ legacy: false }).as('bigNumberChartData');
|
||||
});
|
||||
|
||||
it('Can change vizType', () => {
|
||||
cy.visitChartByName('Daily Totals');
|
||||
cy.verifySliceSuccess({ waitAlias: '@tableChartData' });
|
||||
|
||||
cy.contains('View all charts').click();
|
||||
|
||||
cy.get('.ant-modal-content').within(() => {
|
||||
cy.get('button').contains('KPI').click(); // change categories
|
||||
cy.get('[role="button"]').contains('Big Number').click();
|
||||
cy.get('button').contains('Select').click();
|
||||
});
|
||||
|
||||
cy.get('button[data-test="run-query-button"]').click();
|
||||
cy.verifySliceSuccess({
|
||||
waitAlias: '@bigNumberChartData',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test datatable', () => {
|
||||
beforeEach(() => {
|
||||
interceptChart({ legacy: false }).as('tableChartData');
|
||||
interceptChart({ legacy: false }).as('lineChartData');
|
||||
cy.visitChartByName('Daily Totals');
|
||||
});
|
||||
it('Data Pane opens and loads results', () => {
|
||||
cy.contains('Results').click();
|
||||
cy.get('[data-test="row-count-label"]').contains('26 rows');
|
||||
cy.get('.ant-empty-description').should('not.exist');
|
||||
});
|
||||
it('Datapane loads view samples', () => {
|
||||
cy.intercept(
|
||||
'**/datasource/samples?force=false&datasource_type=table&datasource_id=*',
|
||||
).as('Samples');
|
||||
cy.contains('Samples').click();
|
||||
cy.wait('@Samples');
|
||||
cy.get('.ant-tabs-tab-active').contains('Samples');
|
||||
cy.get('[data-test="row-count-label"]').contains('1k rows');
|
||||
cy.get('.ant-empty-description').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Groupby control', () => {
|
||||
it('Set groupby', () => {
|
||||
interceptChart({ legacy: false }).as('chartData');
|
||||
|
||||
cy.visitChartByName('Num Births Trend');
|
||||
cy.verifySliceSuccess({ waitAlias: '@chartData' });
|
||||
|
||||
cy.get('[data-test=groupby]')
|
||||
.contains('Drop columns here or click')
|
||||
.click();
|
||||
cy.get('[id="adhoc-metric-edit-tabs-tab-simple"]').click();
|
||||
cy.get('input[aria-label="Column"]').click();
|
||||
cy.get('input[aria-label="Column"]').type('state{enter}');
|
||||
cy.get('[data-test="ColumnEdit#save"]').contains('Save').click();
|
||||
|
||||
cy.get('button[data-test="run-query-button"]').click();
|
||||
cy.verifySliceSuccess({ waitAlias: '@chartData' });
|
||||
});
|
||||
});
|
||||
@@ -33,7 +33,6 @@ module.exports = {
|
||||
'^@superset-ui/([^/]+)$': '<rootDir>/node_modules/@superset-ui/$1/src',
|
||||
// mapping @apache-superset/core to local package
|
||||
'^@apache-superset/core$': '<rootDir>/packages/superset-core/src',
|
||||
'^@apache-superset/core/(.*)$': '<rootDir>/packages/superset-core/src/$1',
|
||||
},
|
||||
testEnvironment: '<rootDir>/spec/helpers/jsDomWithFetchAPI.ts',
|
||||
modulePathIgnorePatterns: ['<rootDir>/packages/generator-superset'],
|
||||
|
||||
52
superset-frontend/orval.config.ts
Normal file
52
superset-frontend/orval.config.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { defineConfig } from 'orval';
|
||||
|
||||
export default defineConfig({
|
||||
superset: {
|
||||
input: {
|
||||
target: './openapi-normalized.json', // Pre-normalized OpenAPI spec
|
||||
validation: false,
|
||||
},
|
||||
output: {
|
||||
mode: 'tags-split',
|
||||
target: './packages/superset-ui-core/src/orval-generated-code',
|
||||
client: 'react-query', // 🚀 Generate TanStack Query hooks!
|
||||
schemas: './packages/superset-ui-core/src/orval-generated-code/types', // Types inside generated folder
|
||||
mock: false,
|
||||
clean: true,
|
||||
prettier: true,
|
||||
override: {
|
||||
mutator: {
|
||||
path: './packages/superset-ui-core/src/api/mutator.ts', // Custom code outside generated folder
|
||||
name: 'customInstance',
|
||||
},
|
||||
query: {
|
||||
useQuery: true, // Generate useQuery hooks
|
||||
useMutation: true, // Generate useMutation hooks
|
||||
useInfinite: false, // Disable infinite queries for now
|
||||
},
|
||||
tags: {
|
||||
charts: true, // Only generate Charts endpoints
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
5577
superset-frontend/package-lock.json
generated
5577
superset-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -44,7 +44,7 @@
|
||||
"build-storybook": "storybook build",
|
||||
"build-translation": "scripts/po2json.sh",
|
||||
"bundle-stats": "cross-env BUNDLE_ANALYZER=true npm run build && npx open-cli ../superset/static/stats/statistics.html",
|
||||
"core:cover": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=4096\" jest --coverage --coverageThreshold='{\"global\":{\"statements\":100,\"branches\":100,\"functions\":100,\"lines\":100}}' --collectCoverageFrom='[\"packages/**/src/**/*.{js,ts}\", \"!packages/superset-ui-demo/**/*\", \"!packages/superset-core/**/*\"]' packages",
|
||||
"core:cover": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=4096\" jest --coverage --coverageThreshold='{\"global\":{\"statements\":100,\"branches\":100,\"functions\":100,\"lines\":100}}' --collectCoverageFrom='[\"packages/**/src/**/*.{js,ts}\", \"!packages/superset-ui-demo/**/*\"]' packages",
|
||||
"cover": "cross-env NODE_ENV=test 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",
|
||||
@@ -63,11 +63,6 @@
|
||||
"plugins:release-conventional": "npm run prune && npm run plugins:build && lerna publish --conventional-commits --create-release github --yes",
|
||||
"plugins:release-from-tag": "npm run prune && npm run plugins:build && lerna publish from-package --yes",
|
||||
"plugins:storybook": "cd packages/superset-ui-demo && npm run storybook",
|
||||
"playwright:test": "playwright test",
|
||||
"playwright:ui": "playwright test --ui",
|
||||
"playwright:headed": "playwright test --headed",
|
||||
"playwright:debug": "playwright test --debug",
|
||||
"playwright:report": "playwright show-report",
|
||||
"prettier": "npm run _prettier -- --write",
|
||||
"prettier-check": "npm run _prettier -- --check",
|
||||
"prod": "npm run build",
|
||||
@@ -76,6 +71,10 @@
|
||||
"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",
|
||||
"test-loud": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80%",
|
||||
"openapi:extract": "cp ../superset/static/openapi.json ./openapi-raw.json",
|
||||
"openapi:normalize": "node scripts/normalize-openapi.js openapi-raw.json openapi-normalized.json",
|
||||
"orval:generate": "orval --config orval.config.ts",
|
||||
"orval:regenerate-client": "npm run openapi:extract && npm run openapi:normalize && npm run orval:generate",
|
||||
"type": "tsc --noEmit",
|
||||
"update-maps": "cd plugins/legacy-plugin-chart-country-map/scripts && jupyter nbconvert --to notebook --execute --inplace --allow-errors --ExecutePreprocessor.timeout=1200 'Country Map GeoJSON Generator.ipynb'",
|
||||
"validate-release": "../RELEASING/validate_this_release.sh"
|
||||
@@ -92,9 +91,9 @@
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@reduxjs/toolkit": "^1.9.3",
|
||||
"@rjsf/core": "^5.24.13",
|
||||
"@rjsf/core": "^5.21.1",
|
||||
"@rjsf/utils": "^5.24.3",
|
||||
"@rjsf/validator-ajv8": "^5.24.13",
|
||||
"@rjsf/validator-ajv8": "^5.24.12",
|
||||
"@scarf/scarf": "^1.4.0",
|
||||
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
|
||||
"@superset-ui/core": "file:./packages/superset-ui-core",
|
||||
@@ -127,25 +126,23 @@
|
||||
"@visx/scale": "^3.5.0",
|
||||
"@visx/tooltip": "^3.0.0",
|
||||
"@visx/xychart": "^3.5.1",
|
||||
"ag-grid-community": "34.2.0",
|
||||
"ag-grid-react": "34.2.0",
|
||||
"antd": "^5.24.6",
|
||||
"chrono-node": "^2.7.8",
|
||||
"classnames": "^2.2.5",
|
||||
"content-disposition": "^0.5.4",
|
||||
"d3-color": "^3.1.0",
|
||||
"d3-scale": "^2.1.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"dom-to-image-more": "^3.6.0",
|
||||
"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",
|
||||
"geolib": "^2.0.24",
|
||||
"geostyler": "^14.1.3",
|
||||
"geostyler-data": "^1.1.0",
|
||||
"geostyler-data": "^1.0.0",
|
||||
"geostyler-openlayers-parser": "^4.3.0",
|
||||
"geostyler-qgis-parser": "2.0.1",
|
||||
"geostyler-style": "7.5.0",
|
||||
@@ -159,7 +156,7 @@
|
||||
"json-bigint": "^1.0.0",
|
||||
"json-stringify-pretty-compact": "^2.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.7.1",
|
||||
"luxon": "^3.5.0",
|
||||
"mapbox-gl": "^3.13.0",
|
||||
"markdown-to-jsx": "^7.7.4",
|
||||
"match-sorter": "^6.3.4",
|
||||
@@ -192,7 +189,7 @@
|
||||
"react-split": "^2.0.9",
|
||||
"react-table": "^7.8.0",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"react-virtualized-auto-sizer": "^1.0.26",
|
||||
"react-virtualized-auto-sizer": "^1.0.25",
|
||||
"react-window": "^1.8.10",
|
||||
"redux": "^4.2.1",
|
||||
"redux-localstorage": "^0.4.1",
|
||||
@@ -214,7 +211,7 @@
|
||||
"@applitools/eyes-storybook": "^3.55.6",
|
||||
"@babel/cli": "^7.27.2",
|
||||
"@babel/compat-data": "^7.28.0",
|
||||
"@babel/core": "^7.28.3",
|
||||
"@babel/core": "^7.26.0",
|
||||
"@babel/eslint-parser": "^7.25.9",
|
||||
"@babel/node": "^7.22.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
@@ -229,12 +226,14 @@
|
||||
"@babel/runtime-corejs3": "^7.28.2",
|
||||
"@babel/types": "^7.26.9",
|
||||
"@cypress/react": "^8.0.2",
|
||||
"@deck.gl/extensions": "^9.1.14",
|
||||
"@deck.gl/mesh-layers": "^9.1.14",
|
||||
"@deck.gl/widgets": "^9.1.14",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/jest": "^11.13.0",
|
||||
"@hot-loader/react-dom": "^17.0.2",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@mihkeleidast/storybook-addon-source": "^1.0.1",
|
||||
"@playwright/test": "^1.49.1",
|
||||
"@storybook/addon-actions": "8.1.11",
|
||||
"@storybook/addon-controls": "8.1.11",
|
||||
"@storybook/addon-essentials": "8.1.11",
|
||||
@@ -250,7 +249,6 @@
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/content-disposition": "^0.5.9",
|
||||
"@types/dom-to-image": "^2.6.7",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/js-levenshtein": "^1.1.3",
|
||||
@@ -266,7 +264,8 @@
|
||||
"@types/react-resizable": "^3.0.8",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-transition-group": "^4.4.12",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.8",
|
||||
"@types/react-ultimate-pagination": "^1.2.4",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.4",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@types/redux-localstorage": "^1.0.8",
|
||||
"@types/redux-mock-store": "^1.0.6",
|
||||
@@ -284,9 +283,10 @@
|
||||
"babel-plugin-typescript-to-proptypes": "^2.0.0",
|
||||
"cheerio": "1.1.0",
|
||||
"copy-webpack-plugin": "^13.0.0",
|
||||
"cross-env": "^10.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^7.0.2",
|
||||
"currencyformatter.js": "^2.2.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-prettier": "^7.2.0",
|
||||
@@ -295,7 +295,7 @@
|
||||
"eslint-plugin-cypress": "^3.6.0",
|
||||
"eslint-plugin-file-progress": "^1.5.0",
|
||||
"eslint-plugin-icons": "file:eslint-rules/eslint-plugin-icons",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-jest": "^27.8.0",
|
||||
"eslint-plugin-jest-dom": "^5.5.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
@@ -309,7 +309,8 @@
|
||||
"eslint-plugin-testing-library": "^6.4.0",
|
||||
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
|
||||
"fetch-mock": "^11.1.5",
|
||||
"fork-ts-checker-webpack-plugin": "^9.1.0",
|
||||
"fork-ts-checker-webpack-plugin": "^9.0.2",
|
||||
"global-box": "^2.0.2",
|
||||
"history": "^5.3.0",
|
||||
"html-webpack-plugin": "^5.6.3",
|
||||
"imports-loader": "^5.0.0",
|
||||
@@ -318,9 +319,11 @@
|
||||
"jest-html-reporter": "^4.3.0",
|
||||
"jest-websocket-mock": "^2.5.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"lerna": "^8.2.3",
|
||||
"lerna": "^8.2.1",
|
||||
"mini-css-extract-plugin": "^2.9.0",
|
||||
"open-cli": "^8.0.0",
|
||||
"openapi3-ts": "^4.5.0",
|
||||
"orval": "^7.11.2",
|
||||
"po2json": "^0.4.5",
|
||||
"prettier": "3.6.2",
|
||||
"prettier-plugin-packagejson": "^2.5.3",
|
||||
@@ -349,6 +352,7 @@
|
||||
"webpack-visualizer-plugin2": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tanstack/react-query": "^4.40.1",
|
||||
"ace-builds": "^1.41.0",
|
||||
"core-js": "^3.38.1",
|
||||
"handlebars": "^4.7.8",
|
||||
@@ -365,7 +369,8 @@
|
||||
"puppeteer": "^22.4.1",
|
||||
"underscore": "^1.13.7",
|
||||
"jspdf": "^3.0.1",
|
||||
"nwsapi": "^2.2.13"
|
||||
"nwsapi": "^2.2.13",
|
||||
"query-string": "^6.14.1"
|
||||
},
|
||||
"readme": "ERROR: No README data found!",
|
||||
"scarfSettings": {
|
||||
|
||||
@@ -22,6 +22,19 @@ To add the package to Superset, go to the `superset-frontend` subdirectory in yo
|
||||
npm i -S ../../<%= packageName %>
|
||||
```
|
||||
|
||||
If your Superset plugin exists in the `superset-frontend` directory and you wish to resolve TypeScript errors about `@superset-ui/core` not being resolved correctly, add the following to your `tsconfig.json` file:
|
||||
|
||||
```
|
||||
"references": [
|
||||
{
|
||||
"path": "../../packages/superset-ui-chart-controls"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/superset-ui-core"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
You may also wish to add the following to the `include` array in `tsconfig.json` to make Superset types available to your plugin:
|
||||
|
||||
```
|
||||
|
||||
@@ -1,19 +1,44 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "lib",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": false,
|
||||
"jsx": "react",
|
||||
"lib": [
|
||||
"dom",
|
||||
"esnext"
|
||||
],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"noEmitOnError": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"outDir": "lib",
|
||||
"pretty": true,
|
||||
"removeComments": false,
|
||||
"strict": true,
|
||||
"target": "es2015",
|
||||
"useDefineForClassFields": false,
|
||||
"composite": true,
|
||||
"declarationMap": true,
|
||||
"rootDir": "src",
|
||||
"skipLibCheck": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"resolveJsonModule": true,
|
||||
"types": ["jest"],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
"lib",
|
||||
"test"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"types/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"yosay": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^10.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"fs-extra": "^11.3.0",
|
||||
"jest": "^30.0.5",
|
||||
"yeoman-test": "^10.1.1"
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
{
|
||||
"name": "@apache-superset/core",
|
||||
"version": "0.0.1-rc4",
|
||||
"version": "0.0.1-rc2",
|
||||
"description": "This package contains UI elements, APIs, and utility functions used by Superset.",
|
||||
"sideEffects": false,
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"module": "esm/index.js",
|
||||
"files": [
|
||||
"esm",
|
||||
"lib"
|
||||
],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.26.4",
|
||||
"@babel/core": "^7.28.3",
|
||||
"@babel/core": "^7.26.9",
|
||||
"@babel/preset-env": "^7.26.9",
|
||||
"@babel/preset-react": "^7.26.3",
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@types/react": "^17.0.83",
|
||||
"install": "^0.13.0",
|
||||
"npm": "^11.1.0",
|
||||
"typescript": "^5.0.0"
|
||||
"npm": "^11.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"antd": "^5.24.6",
|
||||
"react": "^17.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "babel src --out-dir lib --extensions \".ts,.tsx\" && tsc --emitDeclarationOnly",
|
||||
"build": "babel src --out-dir lib --extensions \".ts,.tsx\"",
|
||||
"type": "tsc --noEmit"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
@@ -31,32 +31,12 @@ import { Contributions } from './contributions';
|
||||
/**
|
||||
* Represents a database column with its name and data type.
|
||||
*/
|
||||
export type Column = {
|
||||
/**
|
||||
* Label of the column
|
||||
*/
|
||||
export declare interface Column {
|
||||
/** The name of the column */
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Column name defined
|
||||
*/
|
||||
column_name: string;
|
||||
|
||||
/**
|
||||
* The data type of the column (e.g., 'INTEGER', 'VARCHAR', 'TIMESTAMP')
|
||||
*/
|
||||
/** The data type of the column (e.g., 'INTEGER', 'VARCHAR', 'TIMESTAMP') */
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* Generic data type format
|
||||
*/
|
||||
type_generic: GenericDataType;
|
||||
|
||||
/**
|
||||
* True if the column is date format
|
||||
*/
|
||||
is_dttm: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a database table with its name and column definitions.
|
||||
@@ -96,45 +76,6 @@ export declare interface Database {
|
||||
schemas: Schema[];
|
||||
}
|
||||
|
||||
// Keep in sync with superset/errors.py
|
||||
export type ErrorLevel = 'info' | 'warning' | 'error';
|
||||
|
||||
/**
|
||||
* Superset error object structure.
|
||||
* Contains details about an error that occurred within Superset.
|
||||
*/
|
||||
export type SupersetError = {
|
||||
/**
|
||||
* Error types, see enum of SupersetErrorType in superset/errors.py
|
||||
*/
|
||||
error_type: string;
|
||||
|
||||
/**
|
||||
* Extra properties based on the error types
|
||||
*/
|
||||
extra: Record<string, any>;
|
||||
|
||||
/**
|
||||
* Level of the error type
|
||||
*/
|
||||
level: ErrorLevel;
|
||||
|
||||
/**
|
||||
* Detail description for the error
|
||||
*/
|
||||
message: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic data types, see enum of the same name in superset/utils/core.py.
|
||||
*/
|
||||
export enum GenericDataType {
|
||||
Numeric = 0,
|
||||
String = 1,
|
||||
Temporal = 2,
|
||||
Boolean = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a type which can release resources, such
|
||||
* as event listening or a timer.
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
* - Global APIs: Functions and events available across the entire SQL Lab interface
|
||||
*/
|
||||
|
||||
import { Event, Database, SupersetError, Column } from './core';
|
||||
import { Event, Database } from './core';
|
||||
|
||||
/**
|
||||
* Represents an SQL editor instance within a SQL Lab tab.
|
||||
@@ -111,126 +111,6 @@ export interface Tab {
|
||||
panels: Panel[];
|
||||
}
|
||||
|
||||
export enum CTASMethod {
|
||||
Table = 'TABLE',
|
||||
View = 'VIEW',
|
||||
}
|
||||
|
||||
export interface CTAS {
|
||||
/**
|
||||
* Create method for CTAS creation request.
|
||||
*/
|
||||
method: CTASMethod;
|
||||
|
||||
/**
|
||||
* Temporary table name for creation using a CTAS query.
|
||||
*/
|
||||
tempTable: string | null;
|
||||
}
|
||||
|
||||
export interface QueryContext {
|
||||
/**
|
||||
* Unique query ID on client side.
|
||||
*/
|
||||
clientId: string;
|
||||
|
||||
/**
|
||||
* Contains CTAS if the query requests table creation.
|
||||
*/
|
||||
ctas: CTAS | null;
|
||||
|
||||
/**
|
||||
* Requested row limit for the query.
|
||||
*/
|
||||
requestedLimit: number | null;
|
||||
|
||||
/**
|
||||
* True if the query execution result will be/was delivered asynchronously
|
||||
*/
|
||||
runAsync?: boolean;
|
||||
|
||||
/**
|
||||
* Start datetime for the query in a numerical timestamp
|
||||
*/
|
||||
startDttm: number;
|
||||
|
||||
/**
|
||||
* The tab instance associated with the request query
|
||||
*/
|
||||
tab: Tab;
|
||||
|
||||
/**
|
||||
* A key-value JSON associated with Jinja template variables
|
||||
*/
|
||||
templateParameters: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface QueryErrorResultContext extends QueryContext {
|
||||
/**
|
||||
* Finished datetime for the query in a numerical timestamp
|
||||
*/
|
||||
endDttm: number;
|
||||
|
||||
/**
|
||||
* Error message returned from DB engine
|
||||
*/
|
||||
errorMessage: string;
|
||||
|
||||
/**
|
||||
* Error details in a SupersetError structure
|
||||
*/
|
||||
errors: SupersetError[] | null;
|
||||
|
||||
/**
|
||||
* Executed SQL after parsing Jinja templates.
|
||||
*/
|
||||
executedSql: string | null;
|
||||
}
|
||||
|
||||
export interface QueryResultContext extends QueryContext {
|
||||
/**
|
||||
* Actual number of rows returned by the query.
|
||||
*/
|
||||
appliedLimit: number;
|
||||
|
||||
/**
|
||||
* Major factor that is determining the row limit of the query results.
|
||||
*/
|
||||
appliedLimitingFactor: string;
|
||||
|
||||
/**
|
||||
* Finished datetime for the query in a numerical timestamp.
|
||||
*/
|
||||
endDttm: number;
|
||||
|
||||
/**
|
||||
* Executed SQL after parsing Jinja templates.
|
||||
*/
|
||||
executedSql: string;
|
||||
|
||||
/**
|
||||
* Remote query id stored in backend.
|
||||
*/
|
||||
remoteId: number;
|
||||
|
||||
/**
|
||||
* Query result data and metadata.
|
||||
*/
|
||||
result: QueryResult;
|
||||
}
|
||||
|
||||
export interface QueryResult {
|
||||
/**
|
||||
* Column metadata associated with the query result.
|
||||
*/
|
||||
columns: Column[];
|
||||
|
||||
/**
|
||||
* Query result data.
|
||||
*/
|
||||
data: Record<string, any>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tab-scoped Events and Functions
|
||||
*
|
||||
@@ -360,30 +240,30 @@ export declare const onDidChangeTabTitle: Event<string>;
|
||||
|
||||
/**
|
||||
* Event fired when a query starts running in the current tab.
|
||||
* Provides the query request state at the time of query execution.
|
||||
* Provides the editor state at the time of query execution.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* onDidQueryRun.event((query) => {
|
||||
* console.log('Query started on database:', query.tab.editor.databaseId);
|
||||
* console.log('Query content:', query.tab.editor.content);
|
||||
* onDidQueryRun.event((editor) => {
|
||||
* console.log('Query started on database:', editor.databaseId);
|
||||
* console.log('Query content:', editor.content);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export declare const onDidQueryRun: Event<QueryContext>;
|
||||
export declare const onDidQueryRun: Event<Editor>;
|
||||
|
||||
/**
|
||||
* Event fired when a running query is stopped in the current tab.
|
||||
* Provides the query request state when the query was stopped.
|
||||
* Provides the editor state when the query was stopped.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* onDidQueryStop.event((query) => {
|
||||
* console.log('Query stopped for database:', query.tab.editor.databaseId);
|
||||
* onDidQueryStop.event((editor) => {
|
||||
* console.log('Query stopped for database:', editor.databaseId);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export declare const onDidQueryStop: Event<QueryContext>;
|
||||
export declare const onDidQueryStop: Event<Editor>;
|
||||
|
||||
/**
|
||||
* Event fired when a query fails in the current tab.
|
||||
@@ -393,12 +273,12 @@ export declare const onDidQueryStop: Event<QueryContext>;
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* onDidQueryFail.event((result) => {
|
||||
* console.error('Query failed:', result.errorMessage);
|
||||
* onDidQueryFail.event((error) => {
|
||||
* console.error('Query failed:', error);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export declare const onDidQueryFail: Event<QueryErrorResultContext>;
|
||||
export declare const onDidQueryFail: Event<string>;
|
||||
|
||||
/**
|
||||
* Event fired when a query succeeds in the current tab.
|
||||
@@ -408,13 +288,12 @@ export declare const onDidQueryFail: Event<QueryErrorResultContext>;
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* onDidQuerySuccess.event((query) => {
|
||||
* console.log('Query succeeded:', query.result.data);
|
||||
* console.log('Query executed content:', query.executedSql);
|
||||
* onDidQuerySuccess.event((result) => {
|
||||
* console.log('Query succeeded:', result);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export declare const onDidQuerySuccess: Event<QueryResultContext>;
|
||||
export declare const onDidQuerySuccess: Event<string>;
|
||||
|
||||
/**
|
||||
* Global Events and Functions
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "lib",
|
||||
"outDir": "lib",
|
||||
"strict": true,
|
||||
"rootDir": "src",
|
||||
"jsx": "preserve",
|
||||
"baseUrl": ".",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"]
|
||||
"include": ["src/**/*.ts*"],
|
||||
"exclude": ["lib"]
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
"lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"@types/react": "*",
|
||||
"lodash": "^4.17.21"
|
||||
"lodash": "^4.17.21",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
|
||||
@@ -43,17 +43,15 @@ export const renameOperator: PostProcessingFactory<PostProcessingRename> = (
|
||||
|
||||
// remove or rename top level of column name(metric name) in the MultiIndex when
|
||||
// 1) at least 1 metric
|
||||
// 2) xAxis exist
|
||||
// 3a) isTimeComparisonValue
|
||||
// 3b-1) dimension exist or multiple time shift metrics exist
|
||||
// 3b-2) truncate_metric in form_data and truncate_metric is true
|
||||
// 2) dimension exist or multiple time shift metrics exist
|
||||
// 3) xAxis exist
|
||||
// 4) truncate_metric in form_data and truncate_metric is true
|
||||
if (
|
||||
metrics.length > 0 &&
|
||||
(columns.length > 0 || timeOffsets.length > 1) &&
|
||||
xAxisLabel &&
|
||||
(isTimeComparisonValue ||
|
||||
((columns.length > 0 || timeOffsets.length > 1) &&
|
||||
truncate_metric !== undefined &&
|
||||
!!truncate_metric))
|
||||
truncate_metric !== undefined &&
|
||||
!!truncate_metric
|
||||
) {
|
||||
const renamePairs: [string, string | null][] = [];
|
||||
if (
|
||||
|
||||
@@ -20,32 +20,20 @@ import { t } from '@superset-ui/core';
|
||||
import { ControlPanelSectionConfig } from '../types';
|
||||
|
||||
export const matrixifyEnableSection: ControlPanelSectionConfig = {
|
||||
label: t('Matrixify'),
|
||||
label: t('Enable matrixify'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
[
|
||||
{
|
||||
name: 'matrixify_enable_horizontal_layout',
|
||||
name: 'matrixify_enabled',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Enable horizontal layout (columns)'),
|
||||
label: t('Enable matrixify'),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
description: t(
|
||||
'Create matrix columns by placing charts side-by-side',
|
||||
'Transform this chart into a matrix/grid of charts based on dimensions or metrics',
|
||||
),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'matrixify_enable_vertical_layout',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Enable vertical layout (rows)'),
|
||||
description: t('Create matrix rows by stacking charts vertically'),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -54,11 +42,9 @@ export const matrixifyEnableSection: ControlPanelSectionConfig = {
|
||||
};
|
||||
|
||||
export const matrixifySection: ControlPanelSectionConfig = {
|
||||
label: t('Cell layout & styling'),
|
||||
label: t('Matrixify'),
|
||||
expanded: false,
|
||||
visibility: ({ controls }) =>
|
||||
controls?.matrixify_enable_vertical_layout?.value === true ||
|
||||
controls?.matrixify_enable_horizontal_layout?.value === true,
|
||||
visibility: ({ controls }) => controls?.matrixify_enabled?.value === true,
|
||||
controlSetRows: [
|
||||
[
|
||||
{
|
||||
@@ -119,10 +105,9 @@ export const matrixifySection: ControlPanelSectionConfig = {
|
||||
};
|
||||
|
||||
export const matrixifyRowSection: ControlPanelSectionConfig = {
|
||||
label: t('Vertical layout (rows)'),
|
||||
label: t('Vertical layout'),
|
||||
expanded: false,
|
||||
visibility: ({ controls }) =>
|
||||
controls?.matrixify_enable_vertical_layout?.value === true,
|
||||
visibility: ({ controls }) => controls?.matrixify_enabled?.value === true,
|
||||
controlSetRows: [
|
||||
['matrixify_show_row_labels'],
|
||||
['matrixify_mode_rows'],
|
||||
@@ -133,14 +118,13 @@ export const matrixifyRowSection: ControlPanelSectionConfig = {
|
||||
['matrixify_topn_metric_rows'],
|
||||
['matrixify_topn_order_rows'],
|
||||
],
|
||||
tabOverride: 'matrixify',
|
||||
tabOverride: 'data',
|
||||
};
|
||||
|
||||
export const matrixifyColumnSection: ControlPanelSectionConfig = {
|
||||
label: t('Horizontal layout (columns)'),
|
||||
label: t('Horizontal layout'),
|
||||
expanded: false,
|
||||
visibility: ({ controls }) =>
|
||||
controls?.matrixify_enable_horizontal_layout?.value === true,
|
||||
visibility: ({ controls }) => controls?.matrixify_enabled?.value === true,
|
||||
controlSetRows: [
|
||||
['matrixify_show_column_headers'],
|
||||
['matrixify_mode_columns'],
|
||||
@@ -151,5 +135,5 @@ export const matrixifyColumnSection: ControlPanelSectionConfig = {
|
||||
['matrixify_topn_metric_columns'],
|
||||
['matrixify_topn_order_columns'],
|
||||
],
|
||||
tabOverride: 'matrixify',
|
||||
tabOverride: 'data',
|
||||
};
|
||||
|
||||
@@ -18,49 +18,21 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { t, validateNonEmpty } from '@superset-ui/core';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { SharedControlConfig } from '../types';
|
||||
import { dndAdhocMetricControl } from './dndControls';
|
||||
import { defineSavedMetrics } from '../utils';
|
||||
|
||||
/**
|
||||
* Matrixify control definitions
|
||||
* Controls for transforming charts into matrix/grid layouts
|
||||
*/
|
||||
|
||||
// Utility function to check if matrixify controls should be visible
|
||||
const isMatrixifyVisible = (
|
||||
controls: any,
|
||||
axis: 'rows' | 'columns',
|
||||
mode?: 'metrics' | 'dimensions',
|
||||
selectionMode?: 'members' | 'topn',
|
||||
) => {
|
||||
const layoutControl = `matrixify_enable_${axis === 'rows' ? 'vertical' : 'horizontal'}_layout`;
|
||||
const modeControl = `matrixify_mode_${axis}`;
|
||||
const selectionModeControl = `matrixify_dimension_selection_mode_${axis}`;
|
||||
|
||||
const isLayoutEnabled = controls?.[layoutControl]?.value === true;
|
||||
|
||||
if (!isLayoutEnabled) return false;
|
||||
|
||||
if (mode) {
|
||||
const isModeMatch = controls?.[modeControl]?.value === mode;
|
||||
if (!isModeMatch) return false;
|
||||
|
||||
if (selectionMode && mode === 'dimensions') {
|
||||
return controls?.[selectionModeControl]?.value === selectionMode;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Initialize the controls object that will be populated dynamically
|
||||
const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
|
||||
|
||||
// Dynamically add axis-specific controls (rows and columns)
|
||||
(['columns', 'rows'] as const).forEach(axisParam => {
|
||||
const axis: 'rows' | 'columns' = axisParam;
|
||||
['columns', 'rows'].forEach(axisParam => {
|
||||
const axis = axisParam; // Capture the value in a local variable
|
||||
|
||||
matrixifyControls[`matrixify_mode_${axis}`] = {
|
||||
type: 'RadioButtonControl',
|
||||
@@ -71,18 +43,17 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
|
||||
['dimensions', t('Dimension members')],
|
||||
],
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) => isMatrixifyVisible(controls, axis),
|
||||
};
|
||||
|
||||
matrixifyControls[`matrixify_${axis}`] = {
|
||||
...dndAdhocMetricControl,
|
||||
label: t(`Metrics`),
|
||||
multi: true,
|
||||
validators: [], // No validation - rely on visibility
|
||||
validators: [], // Not required
|
||||
// description: t(`Select metrics for ${axis}`),
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) => isMatrixifyVisible(controls, axis, 'metrics'),
|
||||
visibility: ({ controls }) =>
|
||||
controls?.[`matrixify_mode_${axis}`]?.value === 'metrics',
|
||||
};
|
||||
|
||||
// Combined dimension and values control
|
||||
@@ -91,9 +62,8 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
|
||||
label: t(`Dimension selection`),
|
||||
description: t(`Select dimension and values`),
|
||||
default: { dimension: '', values: [] },
|
||||
validators: [], // No validation - rely on visibility
|
||||
validators: [], // Not required
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
shouldMapStateToProps: (prevState, state) => {
|
||||
// Recalculate when any relevant form_data field changes
|
||||
const fieldsToCheck = [
|
||||
@@ -112,40 +82,24 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
|
||||
const getValue = (key: string, defaultValue?: any) =>
|
||||
form_data?.[key] ?? controls?.[key]?.value ?? defaultValue;
|
||||
|
||||
const selectionMode = getValue(
|
||||
`matrixify_dimension_selection_mode_${axis}`,
|
||||
'members',
|
||||
);
|
||||
|
||||
const isVisible = isMatrixifyVisible(controls, axis, 'dimensions');
|
||||
|
||||
// Validate dimension is selected when visible
|
||||
const dimensionValidator = (value: any) => {
|
||||
if (!value?.dimension) {
|
||||
return t('Dimension is required');
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Additional validation for topN mode
|
||||
const validators = isVisible
|
||||
? [dimensionValidator, validateNonEmpty]
|
||||
: [];
|
||||
|
||||
return {
|
||||
datasource,
|
||||
selectionMode,
|
||||
selectionMode: getValue(
|
||||
`matrixify_dimension_selection_mode_${axis}`,
|
||||
'members',
|
||||
),
|
||||
topNMetric: getValue(`matrixify_topn_metric_${axis}`),
|
||||
topNValue: getValue(`matrixify_topn_value_${axis}`),
|
||||
topNOrder: getValue(`matrixify_topn_order_${axis}`),
|
||||
formData: form_data,
|
||||
validators,
|
||||
};
|
||||
},
|
||||
visibility: ({ controls }) =>
|
||||
isMatrixifyVisible(controls, axis, 'dimensions'),
|
||||
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions',
|
||||
};
|
||||
|
||||
// Dimension picker for TopN mode (just dimension, no values)
|
||||
// NOTE: This is now handled by matrixify_dimension control, so hiding it
|
||||
matrixifyControls[`matrixify_topn_dimension_${axis}`] = {
|
||||
type: 'SelectControl',
|
||||
label: t('Dimension'),
|
||||
@@ -173,67 +127,33 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
|
||||
['topn', t('Top n')],
|
||||
],
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) =>
|
||||
isMatrixifyVisible(controls, axis, 'dimensions'),
|
||||
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions',
|
||||
};
|
||||
|
||||
// TopN controls
|
||||
matrixifyControls[`matrixify_topn_value_${axis}`] = {
|
||||
type: 'NumberControl',
|
||||
type: 'TextControl',
|
||||
label: t(`Number of top values`),
|
||||
description: t(`How many top values to select`),
|
||||
default: 10,
|
||||
isInt: true,
|
||||
validators: [],
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) =>
|
||||
isMatrixifyVisible(controls, axis, 'dimensions', 'topn'),
|
||||
mapStateToProps: ({ controls }) => {
|
||||
const isVisible = isMatrixifyVisible(
|
||||
controls,
|
||||
axis,
|
||||
'dimensions',
|
||||
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions' &&
|
||||
controls?.[`matrixify_dimension_selection_mode_${axis}`]?.value ===
|
||||
'topn',
|
||||
);
|
||||
|
||||
return {
|
||||
validators: isVisible ? [validateNonEmpty] : [],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
matrixifyControls[`matrixify_topn_metric_${axis}`] = {
|
||||
...dndAdhocMetricControl,
|
||||
label: t(`Metric for ordering`),
|
||||
multi: false,
|
||||
validators: [],
|
||||
validators: [], // Not required
|
||||
description: t(`Metric to use for ordering Top N values`),
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) =>
|
||||
isMatrixifyVisible(controls, axis, 'dimensions', 'topn'),
|
||||
mapStateToProps: (state, controlState) => {
|
||||
const { controls, datasource } = state;
|
||||
const isVisible = isMatrixifyVisible(
|
||||
controls,
|
||||
axis,
|
||||
'dimensions',
|
||||
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions' &&
|
||||
controls?.[`matrixify_dimension_selection_mode_${axis}`]?.value ===
|
||||
'topn',
|
||||
);
|
||||
|
||||
const originalProps =
|
||||
dndAdhocMetricControl.mapStateToProps?.(state, controlState) || {};
|
||||
|
||||
return {
|
||||
...originalProps,
|
||||
columns: datasource?.columns || [],
|
||||
savedMetrics: defineSavedMetrics(datasource),
|
||||
datasource,
|
||||
datasourceType: datasource?.type,
|
||||
validators: isVisible ? [validateNonEmpty] : [],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
matrixifyControls[`matrixify_topn_order_${axis}`] = {
|
||||
@@ -244,10 +164,10 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
|
||||
['asc', t('Ascending')],
|
||||
['desc', t('Descending')],
|
||||
],
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) =>
|
||||
isMatrixifyVisible(controls, axis, 'dimensions', 'topn'),
|
||||
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions' &&
|
||||
controls?.[`matrixify_dimension_selection_mode_${axis}`]?.value ===
|
||||
'topn',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -293,22 +213,15 @@ matrixifyControls.matrixify_charts_per_row = {
|
||||
!controls?.matrixify_fit_columns_dynamically?.value,
|
||||
};
|
||||
|
||||
matrixifyControls.matrixify_enable_vertical_layout = {
|
||||
// Main enable control
|
||||
matrixifyControls.matrixify_enabled = {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Enable vertical layout (rows)'),
|
||||
description: t('Create matrix rows by stacking charts vertically'),
|
||||
label: t('Enable matrixify'),
|
||||
description: t(
|
||||
'Transform this chart into a matrix/grid of charts based on dimensions or metrics',
|
||||
),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
};
|
||||
|
||||
matrixifyControls.matrixify_enable_horizontal_layout = {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Enable horizontal layout (columns)'),
|
||||
description: t('Create matrix columns by placing charts side-by-side'),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
};
|
||||
|
||||
// Cell title control for Matrixify
|
||||
@@ -321,8 +234,8 @@ matrixifyControls.matrixify_cell_title_template = {
|
||||
default: '',
|
||||
renderTrigger: true,
|
||||
visibility: ({ controls }) =>
|
||||
controls?.matrixify_enable_vertical_layout?.value === true ||
|
||||
controls?.matrixify_enable_horizontal_layout?.value === true,
|
||||
(controls?.matrixify_mode_rows?.value ||
|
||||
controls?.matrixify_mode_columns?.value) !== undefined,
|
||||
};
|
||||
|
||||
// Matrix display controls
|
||||
@@ -332,9 +245,9 @@ matrixifyControls.matrixify_show_row_labels = {
|
||||
description: t('Display labels for each row on the left side of the matrix'),
|
||||
default: true,
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) =>
|
||||
controls?.matrixify_enable_vertical_layout?.value === true,
|
||||
(controls?.matrixify_mode_rows?.value ||
|
||||
controls?.matrixify_mode_columns?.value) !== undefined,
|
||||
};
|
||||
|
||||
matrixifyControls.matrixify_show_column_headers = {
|
||||
@@ -343,9 +256,9 @@ matrixifyControls.matrixify_show_column_headers = {
|
||||
description: t('Display headers for each column at the top of the matrix'),
|
||||
default: true,
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) =>
|
||||
controls?.matrixify_enable_horizontal_layout?.value === true,
|
||||
(controls?.matrixify_mode_rows?.value ||
|
||||
controls?.matrixify_mode_columns?.value) !== undefined,
|
||||
};
|
||||
|
||||
export { matrixifyControls };
|
||||
|
||||
@@ -17,7 +17,11 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { ensureIsArray, GenericDataType, ValueOf } from '@superset-ui/core';
|
||||
import { ControlPanelState, isDataset, isQueryResponse } from '../types';
|
||||
import {
|
||||
ControlPanelState,
|
||||
isDataset,
|
||||
isQueryResponse,
|
||||
} from '@superset-ui/chart-controls';
|
||||
|
||||
export function checkColumnType(
|
||||
columnName: string,
|
||||
|
||||
@@ -160,44 +160,6 @@ test('should add renameOperator if exists derived metrics', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should add renameOperator if isTimeComparisonValue without columns', () => {
|
||||
[
|
||||
ComparisonType.Difference,
|
||||
ComparisonType.Ratio,
|
||||
ComparisonType.Percentage,
|
||||
].forEach(type => {
|
||||
expect(
|
||||
renameOperator(
|
||||
{
|
||||
...formData,
|
||||
...{
|
||||
comparison_type: type,
|
||||
time_compare: ['1 year ago'],
|
||||
},
|
||||
},
|
||||
{
|
||||
...queryObject,
|
||||
...{
|
||||
columns: [],
|
||||
metrics: ['sum(val)', 'avg(val2)'],
|
||||
},
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
operation: 'rename',
|
||||
options: {
|
||||
columns: {
|
||||
[`${type}__avg(val2)__avg(val2)__1 year ago`]:
|
||||
'avg(val2), 1 year ago',
|
||||
[`${type}__sum(val)__sum(val)__1 year ago`]: 'sum(val), 1 year ago',
|
||||
},
|
||||
inplace: true,
|
||||
level: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should add renameOperator if x_axis does not exist', () => {
|
||||
expect(
|
||||
renameOperator(
|
||||
|
||||
@@ -2,8 +2,18 @@
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": ["**/*", "../types/**/*", "../../../types/**/*"]
|
||||
"include": [
|
||||
"**/*",
|
||||
"../types/**/*",
|
||||
"../../../types/**/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
"declarationDir": "lib",
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"],
|
||||
"exclude": [
|
||||
"lib",
|
||||
"test"
|
||||
],
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"types/**/*",
|
||||
"../../types/**/*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../superset-core" },
|
||||
{ "path": "../superset-ui-core" }
|
||||
{
|
||||
"path": "../superset-ui-core"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -24,25 +24,26 @@
|
||||
"lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@babel/runtime": "^7.28.2",
|
||||
"@fontsource/fira-code": "^5.2.6",
|
||||
"@fontsource/inter": "^5.2.6",
|
||||
"@tanstack/react-query": "^4.40.1",
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@visx/responsive": "^3.12.0",
|
||||
"ace-builds": "^1.43.1",
|
||||
"ag-grid-community": "34.2.0",
|
||||
"ag-grid-react": "34.2.0",
|
||||
"ag-grid-community": "^34.0.2",
|
||||
"ag-grid-react": "34.0.2",
|
||||
"brace": "^0.11.1",
|
||||
"classnames": "^2.2.5",
|
||||
"csstype": "^3.1.3",
|
||||
"core-js": "^3.38.1",
|
||||
"csstype": "^3.1.3",
|
||||
"d3-format": "^1.3.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-time": "^3.1.0",
|
||||
"d3-time-format": "^4.1.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dompurify": "^3.2.4",
|
||||
"fetch-retry": "^6.0.0",
|
||||
"handlebars": "^4.7.8",
|
||||
@@ -52,13 +53,13 @@
|
||||
"pretty-ms": "^9.2.0",
|
||||
"re-resizable": "^6.11.2",
|
||||
"react-ace": "^14.0.1",
|
||||
"react-js-cron": "^5.2.0",
|
||||
"react-draggable": "^4.5.0",
|
||||
"react-resize-detector": "^7.1.2",
|
||||
"react-syntax-highlighter": "^15.6.6",
|
||||
"react-ultimate-pagination": "^1.3.2",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-js-cron": "^5.2.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-resize-detector": "^7.1.2",
|
||||
"react-syntax-highlighter": "^15.4.5",
|
||||
"react-ultimate-pagination": "^1.3.2",
|
||||
"regenerator-runtime": "^0.14.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
@@ -66,7 +67,6 @@
|
||||
"reselect": "^5.1.1",
|
||||
"rison": "^0.1.1",
|
||||
"seedrandom": "^3.0.5",
|
||||
"@visx/responsive": "^3.12.0",
|
||||
"xss": "^1.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -76,13 +76,13 @@
|
||||
"@types/d3-scale": "^2.1.1",
|
||||
"@types/d3-time": "^3.0.4",
|
||||
"@types/d3-time-format": "^4.0.3",
|
||||
"@types/react-table": "^7.7.20",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/jquery": "^3.5.8",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/math-expression-evaluator": "^1.3.3",
|
||||
"@types/node": "^22.10.3",
|
||||
"@types/prop-types": "^15.7.15",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/react-table": "^7.7.20",
|
||||
"@types/rison": "0.1.0",
|
||||
"@types/seedrandom": "^3.0.8",
|
||||
"fetch-mock": "^11.1.4",
|
||||
@@ -91,7 +91,6 @@
|
||||
"timezone-mock": "1.3.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"antd": "^5.24.6",
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
@@ -104,6 +103,7 @@
|
||||
"@types/react-loadable": "*",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@types/tinycolor2": "*",
|
||||
"antd": "^5.24.6",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
@@ -115,24 +115,29 @@
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./lib/index.d.ts",
|
||||
"import": "./esm/index.js",
|
||||
"require": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts"
|
||||
"require": "./lib/index.js"
|
||||
},
|
||||
"./components/*": {
|
||||
"types": "./lib/components/*/index.d.ts",
|
||||
"import": "./esm/components/*/index.js",
|
||||
"require": "./lib/components/*/index.js",
|
||||
"types": "./lib/components/*/index.d.ts"
|
||||
"require": "./lib/components/*/index.js"
|
||||
},
|
||||
"./components": {
|
||||
"types": "./lib/components/index.d.ts",
|
||||
"import": "./esm/components/index.js",
|
||||
"require": "./lib/components/index.js",
|
||||
"types": "./lib/components/index.d.ts"
|
||||
"require": "./lib/components/index.js"
|
||||
},
|
||||
"./utils/*": {
|
||||
"types": "./lib/utils/*.d.ts",
|
||||
"import": "./esm/utils/*.js",
|
||||
"require": "./lib/utils/*.js",
|
||||
"types": "./lib/utils/*.d.ts"
|
||||
"require": "./lib/utils/*.js"
|
||||
},
|
||||
"./api": {
|
||||
"types": "./lib/api/index.d.ts",
|
||||
"import": "./esm/api/index.js",
|
||||
"require": "./lib/api/index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
144
superset-frontend/packages/superset-ui-core/src/api/README.md
Normal file
144
superset-frontend/packages/superset-ui-core/src/api/README.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Orval API Client Generation
|
||||
|
||||
## Current Status: POC Complete ✅
|
||||
|
||||
We've successfully proven that orval can generate **type-safe API clients** for Superset!
|
||||
|
||||
### What Works Now
|
||||
- ✅ **Orval integration** - Installed and configured in superset-ui-core
|
||||
- ✅ **Client generation** - Working generation from simplified OpenAPI spec
|
||||
- ✅ **Type safety** - Full TypeScript interfaces and functions
|
||||
- ✅ **POC component** - Demonstrates usage patterns vs SupersetClient
|
||||
|
||||
### Generated Client Structure
|
||||
```
|
||||
src/api/generated/
|
||||
├── superset-api.ts # API functions (getCharts, createChart, etc.)
|
||||
└── types/ # TypeScript interfaces (Chart, ChartPayload, etc.)
|
||||
```
|
||||
|
||||
### Usage Example
|
||||
```typescript
|
||||
// Old SupersetClient way ❌
|
||||
const response = await SupersetClient.get({
|
||||
endpoint: '/api/v1/charts/',
|
||||
searchParams: { page: 1 }
|
||||
});
|
||||
const data: any = response.json; // No type safety!
|
||||
|
||||
// New orval-generated way ✅
|
||||
import { getCharts, type Chart } from './generated/superset-api';
|
||||
const response = await getCharts({ page: 1, page_size: 25 });
|
||||
const charts: Chart[] = response.data.result || []; // Fully typed!
|
||||
```
|
||||
|
||||
## The Full API Scope 🚀
|
||||
|
||||
Superset's OpenAPI spec contains **176 unique endpoints** covering:
|
||||
|
||||
### Major API Categories
|
||||
- **Charts** (`/api/v1/chart/`) - Chart CRUD, data, screenshots, favorites
|
||||
- **Dashboards** (`/api/v1/dashboard/`) - Dashboard management, filters, permissions
|
||||
- **Datasets** (`/api/v1/dataset/`) - Dataset management, columns, metrics
|
||||
- **Databases** (`/api/v1/database/`) - Database connections, schemas, tables
|
||||
- **Security** (`/api/v1/security/`) - Authentication, permissions, roles
|
||||
- **SQL Lab** (`/api/v1/sqllab/`) - Query execution, saved queries
|
||||
- **And many more** - Reports, annotations, themes, advanced data types...
|
||||
|
||||
### Example Generated Functions (Full API)
|
||||
```typescript
|
||||
// Charts
|
||||
getCharts(params?: GetChartsParams): Promise<ChartsResponse>
|
||||
createChart(payload: ChartPayload): Promise<Chart>
|
||||
updateChart(id: number, payload: ChartUpdatePayload): Promise<Chart>
|
||||
deleteChart(id: number): Promise<void>
|
||||
getChartData(id: number, params: QueryParams): Promise<ChartDataResponse>
|
||||
|
||||
// Dashboards
|
||||
getDashboards(params?: GetDashboardsParams): Promise<DashboardsResponse>
|
||||
createDashboard(payload: DashboardPayload): Promise<Dashboard>
|
||||
getDashboardCharts(id: number): Promise<Chart[]>
|
||||
exportDashboard(id: number): Promise<DashboardExportResponse>
|
||||
|
||||
// Datasets
|
||||
getDatasets(params?: GetDatasetsParams): Promise<DatasetsResponse>
|
||||
getDatasetColumns(id: number): Promise<DatasetColumn[]>
|
||||
updateDatasetColumn(datasetId: number, columnId: number, payload: ColumnPayload): Promise<DatasetColumn>
|
||||
|
||||
// SQL Lab
|
||||
executeQuery(payload: QueryPayload): Promise<QueryResult>
|
||||
getSavedQueries(params?: SavedQueryParams): Promise<SavedQueriesResponse>
|
||||
```
|
||||
|
||||
## Current Challenge: OpenAPI Spec Validation
|
||||
|
||||
The full Superset OpenAPI spec has validation issues that prevent complete generation:
|
||||
1. **Missing schema references** (e.g., `DashboardScreenshotPostSchema`)
|
||||
2. **Invalid array schemas** (arrays without `items` property)
|
||||
3. **Complex nested references** causing circular dependencies
|
||||
|
||||
## Next Steps for Full Implementation
|
||||
|
||||
### 1. Schema Validation Fix
|
||||
```bash
|
||||
# Approach 1: Fix upstream in Flask-AppBuilder
|
||||
# - Update marshmallow schemas in Python code
|
||||
# - Ensure all references are properly defined
|
||||
|
||||
# Approach 2: Post-processing script
|
||||
# - Automatically fix common issues in generated spec
|
||||
# - Handle missing references and invalid arrays
|
||||
|
||||
# Approach 3: Gradual rollout
|
||||
# - Generate clients for individual API sections
|
||||
# - Combine multiple smaller specs
|
||||
```
|
||||
|
||||
### 2. Enhanced Configuration
|
||||
```typescript
|
||||
// orval.config.ts for full API
|
||||
export default defineConfig({
|
||||
charts: {
|
||||
input: { target: './specs/charts-api.json' },
|
||||
output: { target: './src/api/charts/' }
|
||||
},
|
||||
dashboards: {
|
||||
input: { target: './specs/dashboards-api.json' },
|
||||
output: { target: './src/api/dashboards/' }
|
||||
},
|
||||
// ... other API sections
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Migration Strategy
|
||||
- **Phase 1**: Fix schema issues, generate complete client
|
||||
- **Phase 2**: Add TanStack Query hooks for caching/optimistic updates
|
||||
- **Phase 3**: Create SupersetClient compatibility layer
|
||||
- **Phase 4**: Gradual migration of existing code
|
||||
|
||||
### 4. Developer Experience Improvements
|
||||
```typescript
|
||||
// With TanStack Query integration
|
||||
const { data: charts, isLoading, error } = useGetCharts({ page: 1 });
|
||||
const createChartMutation = useCreateChart({
|
||||
onSuccess: () => queryClient.invalidateQueries(['charts'])
|
||||
});
|
||||
|
||||
// With automatic retries, caching, background refetch
|
||||
const { data } = useGetDashboard(dashboardId, {
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
retry: 3
|
||||
});
|
||||
```
|
||||
|
||||
## Value Proposition Confirmed ✅
|
||||
|
||||
Even with the current limitations, the POC demonstrates **massive value**:
|
||||
|
||||
1. **Type Safety**: Compile-time API validation
|
||||
2. **Developer Experience**: Auto-completion, parameter validation
|
||||
3. **Maintainability**: Automatic sync with backend changes
|
||||
4. **Performance**: Modern fetch-based client with caching potential
|
||||
5. **Scale**: 176 endpoints → thousands of type-safe functions
|
||||
|
||||
**Bottom Line**: Once we resolve the schema issues, we'll have a **game-changing** improvement to Superset's frontend API layer! 🚀
|
||||
56
superset-frontend/packages/superset-ui-core/src/api/index.ts
Normal file
56
superset-frontend/packages/superset-ui-core/src/api/index.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clean API exports for orval-generated TanStack Query hooks
|
||||
*
|
||||
* Usage:
|
||||
* import { useGetCharts, useCreateChart } from '@superset-ui/core/api';
|
||||
* import type { Chart, ChartCreatePayload } from '@superset-ui/core/api';
|
||||
*/
|
||||
|
||||
// Re-export all generated hooks and functions
|
||||
// Custom wrapper for useGetCharts to flatten the parameter structure
|
||||
import { useGetApiV1Chart } from '../orval-generated-code/charts/charts';
|
||||
import type { GetListSchema } from '../orval-generated-code/types';
|
||||
|
||||
export * from '../orval-generated-code/charts/charts';
|
||||
|
||||
// Re-export all generated types (already consolidated in types/index.ts)
|
||||
export * from '../orval-generated-code/types';
|
||||
|
||||
// Re-export custom mutator for advanced usage
|
||||
export { customInstance } from './mutator';
|
||||
|
||||
// POC-compatible aliases for easier migration
|
||||
export {
|
||||
usePostApiV1Chart as useCreateChart,
|
||||
usePutApiV1ChartPk as useUpdateChart,
|
||||
useDeleteApiV1ChartPk as useDeleteChart,
|
||||
} from '../orval-generated-code/charts/charts';
|
||||
|
||||
export const useGetCharts = (params: GetListSchema) =>
|
||||
// Pass params directly - the mutator will wrap them in q=rison.encode(params)
|
||||
useGetApiV1Chart(params as any);
|
||||
|
||||
// Type aliases for POC compatibility
|
||||
export type {
|
||||
ChartDataRestApiPost as ChartCreatePayload,
|
||||
ChartDataRestApiPut as ChartUpdatePayload,
|
||||
} from '../orval-generated-code/types';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user