Compare commits

..
Author SHA1 Message Date
Joe LiandClaude 2ce3b10b8b test(auth): add comprehensive test coverage for OAuth/LDAP APP_ROOT fix
Significantly improved test quality from 6/10 to 9/10 by adding comprehensive test coverage:

 Critical Improvements:
- Fixed SupersetClient mock issue using jest.spyOn() pattern
- Enabled critical regression test that verifies no double-prefixing occurs
- Added full APP_ROOT test coverage for Register component

📊 Test Statistics:
- Login tests: increased from 8 to 28 tests (250% increase)
- Register tests: increased from 3 to 7 tests (133% increase)
- Total: 35 comprehensive tests (218% increase from 11)

🧪 New Test Categories Added:

1. Edge Cases:
   - Empty providers array handling
   - Invalid provider objects (null names, missing icons)
   - Special characters in provider names
   - Very long provider names
   - Mixed auth types
   - Null/undefined configurations

2. Integration Tests:
   - Full login flow with session storage verification
   - Loading state during form submission
   - Form validation (required fields)
   - Password visibility toggle
   - Form reset on navigation

3. User Interaction Tests:
   - Keyboard navigation for accessibility
   - Tab order verification
   - Uses @testing-library/user-event for better simulation

4. Error State Tests:
   - Network error handling
   - Malformed bootstrap data
   - Missing sessionStorage handling
   - OAuth providers with malformed URLs
   - Error message display from session storage

This ensures the OAuth/LDAP APP_ROOT fix is thoroughly tested and prevents regressions.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-30 09:28:21 -07:00
Joe LiandClaude 0e3385b9e7 fix(auth): Fix SupersetClient mock to avoid TypeScript build dependencies
- Remove jest.requireActual() which tries to load unbuilt @superset-ui/core
- Use simple mock pattern like other tests in the codebase
- Prevents TypeScript compilation issues in pre-commit hooks

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-25 13:05:38 -07:00
Joe LiandClaude ee5faac08b fix(auth): Remove double-prefix in SupersetClient.postForm call
- Remove ensureAppRoot() from SupersetClient.postForm('/login/') call
- SupersetClient already adds appRoot internally via getUrl() method
- Add explanatory comment to prevent future double-prefix regressions
- Add regression test to verify form submission uses bare endpoint
- Maintains existing tests for OAuth/LDAP button hrefs that need ensureAppRoot

Prevents double-prefixing bug where form submission would target
/superset/superset/login/ instead of /superset/login/ when deployed
with custom APP_ROOT configuration.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-25 13:05:38 -07:00
Joe LiandClaude 7a5553a6f8 fix(auth): OAuth and LDAP login URLs respect APP_ROOT configuration
- Fix Login page OAuth/OID provider URLs to use ensureAppRoot()
- Fix Login page form submission URL to use ensureAppRoot()
- Fix Login page registration button URL to use ensureAppRoot()
- Fix Register page login button URL to use ensureAppRoot()
- Add comprehensive tests for both app root and non-app root scenarios

Resolves authentication issues when Superset is deployed with a custom
APP_ROOT path (e.g., /superset) by ensuring all login URLs include
the proper application root prefix.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-25 12:58:16 -07:00
Nikita RybalchenkoandGitHub 77a5969dc1 feat(pdf): add configurable PDF compression level support (#34096) 2025-09-25 08:29:54 -07:00
GeidōandGitHub fb9032c05c fix: Cosmetic issues (#35122) 2025-09-25 17:24:34 +03:00
Mehmet Salih YavuzandGitHub 7a9dbfe879 fix(BuilderComponentPane): navigation tabs padding (#35213) 2025-09-25 16:59:48 +03:00
Giulio PiccoloGitHubbito-code-review[bot] <188872107+bito-code-review[bot]@users.noreply.github.com>
0de78d8203 fix(deck.gl): ensure min/max values are included in polygon map legend breakpoints (#35033)
Co-authored-by: bito-code-review[bot] <188872107+bito-code-review[bot]@users.noreply.github.com>
2025-09-25 14:30:44 +03:00
abc2d46fed refactor: remove obsolete Flask flash messaging system (#35237)
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-25 00:05:16 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
927cc1cda1 chore(deps): bump tar-fs from 3.1.0 to 3.1.1 in /superset-frontend (#35272)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-24 22:29:13 -07:00
JUST.in DO ITandGitHub 7f3840557a chore(react18): Migrate legacy react methods (#34892) 2025-09-24 12:34:22 -07:00
JUST.in DO ITandGitHub 0defcb604b chore(sqllab): remove unused json param (#35065) 2025-09-24 10:26:55 -07:00
Beto DealmeidaandGitHub 94686ddfbe fix(SQL Lab): syncTable on new tabs (#35216) 2025-09-24 11:58:54 -04:00
SBIN2010andGitHub ec322dfd8d fix(Mixed Chart): Tooltip incorrectly displays numbers with optional Y-axis format and showQueryIdentifiers set to true (#35224) 2025-09-24 17:44:01 +03:00
Mehmet Salih YavuzandGitHub cb88d886c7 fix(PropertiesModal): do not show validation errors while loading (#35215) 2025-09-24 10:52:16 +03:00
608e3baf43 feat(build): auto-rebuild/check TypeScript types for packages/plugins in webpack (#35240)
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-23 19:22:59 -07:00
b6f6b75348 fix(dashboard): update header border to use colorBorder token (#35199)
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-23 18:08:17 -07:00
a5ad1d186c docs: Add instruction to avoid time-specific language in code comments (#35200)
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-23 18:07:59 -07:00
Beto DealmeidaandGitHub db88d80b3f fix: docker-compose-image-tag (#35246) 2025-09-23 14:57:53 -07:00
Rafael BenitezandGitHub 4b71adaa9c feat(themes): Adding SupersetText support to Themes Modal (#35248) 2025-09-23 22:23:57 +02:00
Mehmet Salih YavuzandGitHub 5fbda3af40 chore(effect): add eslint plugin to reduce rerenders (#35223) 2025-09-23 22:31:10 +03:00
Tadeh AlexaniandGitHub bc0c40c80e feat(helm): Allow multi-database connection support (#34327) 2025-09-23 11:46:52 -07:00
Michael S. MolinaandGitHub f030d658c5 chore: Bumps @apache-superset/core to 0.0.1-rc5 (#35247) 2025-09-23 15:24:29 -03:00
e85337c543 fix(docs): escape comparison operators in MDX files to resolve build errors (#35185)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Sam Firke <sfirke@users.noreply.github.com>
2025-09-23 10:49:28 -07:00
fe7f8062f3 fix: Enable Playwright migration with graceful Selenium fallback (#35063)
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-23 10:47:45 -07:00
Damian PendrakandGitHub dce74014da refactor(deckgl): update deck.gl charts to use new api (#34859) 2025-09-23 10:42:28 -07:00
Beto DealmeidaandGitHub 619b341cad fix: docker-compose file and superset-core (#35235) 2025-09-23 10:24:23 -07:00
Michael S. MolinaandGitHub 9b6876be62 fix: Typescript declaration files (#35244) 2025-09-23 14:22:28 -03:00
Mehmet Salih YavuzandGitHub c601341520 fix(ConditionalFormattingControl): icon color in dark mode (#35243) 2025-09-23 20:16:29 +03:00
Michael S. MolinaandGitHub 78faaee685 chore: Reference GenericDataType from @apache-superset/core (#35214) 2025-09-23 10:12:18 -07:00
DimitriandGitHub 4027bad1d6 docs(oracle): update driver (#35242) 2025-09-23 13:53:58 +03:00
Levis MboteandGitHub ce55cc7dd7 fix(table-chart): fix cell bar visibility in dark theme (#35211) 2025-09-23 00:06:03 -07:00
Gabriel Torres RuizandGitHub 48e1b1ff2c feat(bug): defensive code to avoid accesing attribute of a NoneType object (#35219) 2025-09-22 10:38:08 -07:00
Beto DealmeidaandGitHub 5ec8f9d886 chore: bump sqlglot to 27.15.2 (#35176) 2025-09-22 12:40:16 -04:00
301 changed files with 12409 additions and 4140 deletions
-69
View File
@@ -1,69 +0,0 @@
name: 'Setup Frontend Environment'
description: 'Set up Node.js v20, npm v11, and install frontend dependencies. Uses Node v20 due to Docker memory constraints in GitHub Actions with v22.'
inputs:
node-version:
description: 'Node.js version to set up. Defaults to reading from .nvmrc file.'
required: false
default: ''
npm-version:
description: 'npm version to install. Defaults to 10.8.1'
required: false
default: '10.8.1'
install-dependencies:
description: 'Whether to install frontend dependencies with npm ci'
required: false
default: 'true'
build-assets:
description: 'Build static assets after installing dependencies'
required: false
default: 'false'
build-instrumented:
description: 'Build instrumented assets for test coverage'
required: false
default: 'false'
install-cypress:
description: 'Install Cypress dependencies in cypress-base directory'
required: false
default: 'false'
runs:
using: 'composite'
steps:
- name: Setup Node.js with npm caching
uses: actions/setup-node@v4
with:
node-version-file: ${{ inputs.node-version != '' && inputs.node-version || './superset-frontend/.nvmrc' }}
cache: 'npm'
cache-dependency-path: './superset-frontend/package-lock.json'
- name: Upgrade npm to v11
shell: bash
run: npm install -g npm@${{ inputs.npm-version }}
- name: Install Frontend Dependencies
if: inputs.install-dependencies == 'true'
shell: bash
run: |
cd superset-frontend
npm ci
- name: Build Static Assets
if: inputs.build-assets == 'true'
shell: bash
run: |
cd superset-frontend
npm run build
- name: Build Instrumented Assets
if: inputs.build-instrumented == 'true'
shell: bash
run: |
cd superset-frontend
npm run build-instrumented
- name: Install Cypress Dependencies
if: inputs.install-cypress == 'true'
shell: bash
run: |
cd superset-frontend/cypress-base
npm ci
+54
View File
@@ -31,6 +31,48 @@ say() {
fi fi
} }
pip-upgrade() {
say "::group::Upgrade pip"
pip install --upgrade pip
say "::endgroup::"
}
# prepare (lint and build) frontend code
npm-install() {
cd "$GITHUB_WORKSPACE/superset-frontend"
# cache-restore npm
say "::group::Install npm packages"
echo "npm: $(npm --version)"
echo "node: $(node --version)"
npm ci
say "::endgroup::"
# cache-save npm
}
build-assets() {
cd "$GITHUB_WORKSPACE/superset-frontend"
say "::group::Build static assets"
npm run build
say "::endgroup::"
}
build-instrumented-assets() {
cd "$GITHUB_WORKSPACE/superset-frontend"
say "::group::Build static assets with JS instrumented for test coverage"
cache-restore instrumented-assets
if [[ -f "$ASSETS_MANIFEST" ]]; then
echo 'Skip frontend build because instrumented static assets already exist.'
else
npm run build-instrumented
cache-save instrumented-assets
fi
say "::endgroup::"
}
setup-postgres() { setup-postgres() {
say "::group::Install dependency for unit tests" say "::group::Install dependency for unit tests"
sudo apt-get update && sudo apt-get install --yes libecpg-dev sudo apt-get update && sudo apt-get install --yes libecpg-dev
@@ -89,6 +131,18 @@ celery-worker() {
say "::endgroup::" say "::endgroup::"
} }
cypress-install() {
cd "$GITHUB_WORKSPACE/superset-frontend/cypress-base"
cache-restore cypress
say "::group::Install Cypress"
npm ci
say "::endgroup::"
cache-save cypress
}
cypress-run-all() { cypress-run-all() {
local USE_DASHBOARD=$1 local USE_DASHBOARD=$1
local APP_ROOT=$2 local APP_ROOT=$2
@@ -16,10 +16,10 @@ jobs:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Setup Frontend Environment - name: Set up Node.js
uses: ./.github/actions/setup-frontend/ uses: actions/setup-node@v4
with: with:
install-dependencies: 'false' node-version: '20'
- name: Install Dependencies - name: Install Dependencies
run: npm install -g @action-validator/core @action-validator/cli --save-dev run: npm install -g @action-validator/core @action-validator/cli --save-dev
+9 -2
View File
@@ -38,8 +38,15 @@ jobs:
echo "HOMEBREW_CELLAR=$HOMEBREW_CELLAR" >>"${GITHUB_ENV}" echo "HOMEBREW_CELLAR=$HOMEBREW_CELLAR" >>"${GITHUB_ENV}"
echo "HOMEBREW_REPOSITORY=$HOMEBREW_REPOSITORY" >>"${GITHUB_ENV}" echo "HOMEBREW_REPOSITORY=$HOMEBREW_REPOSITORY" >>"${GITHUB_ENV}"
brew install norwoodj/tap/helm-docs brew install norwoodj/tap/helm-docs
- name: Setup Frontend Environment - name: Setup Node.js
uses: ./.github/actions/setup-frontend/ uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Frontend Dependencies
run: |
cd superset-frontend
npm ci
- name: Install Docs Dependencies - name: Install Docs Dependencies
run: | run: |
+33 -2
View File
@@ -40,9 +40,40 @@ jobs:
git fetch --prune --unshallow git fetch --prune --unshallow
git tag -d `git tag | grep -E '^trigger-'` git tag -d `git tag | grep -E '^trigger-'`
- name: Setup Frontend Environment - name: Install Node.js
if: env.HAS_TAGS if: env.HAS_TAGS
uses: ./.github/actions/setup-frontend/ uses: actions/setup-node@v4
with:
node-version-file: './superset-frontend/.nvmrc'
- name: Cache npm
if: env.HAS_TAGS
uses: actions/cache@v4
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.OS }}-node-
${{ runner.OS }}-
- name: Get npm cache directory path
if: env.HAS_TAGS
id: npm-cache-dir-path
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- name: Cache npm
if: env.HAS_TAGS
uses: actions/cache@v4
id: npm-cache # use this to check for `cache-hit` (`steps.npm-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Install dependencies
if: env.HAS_TAGS
working-directory: ./superset-frontend
run: npm ci
- name: Run unit tests - name: Run unit tests
if: env.HAS_TAGS if: env.HAS_TAGS
working-directory: ./superset-frontend working-directory: ./superset-frontend
+25 -34
View File
@@ -61,26 +61,11 @@ jobs:
console.log(`📊 Permission level for ${actor}: ${permission.permission}`); console.log(`📊 Permission level for ${actor}: ${permission.permission}`);
const authorized = ['write', 'admin'].includes(permission.permission); const authorized = ['write', 'admin'].includes(permission.permission);
// Handle synchronize events // If this is a synchronize event from unauthorized user, check if Showtime is active and set blocked label
if (context.eventName === 'pull_request_target' && context.payload.action === 'synchronize') { if (!authorized && context.eventName === 'pull_request_target' && context.payload.action === 'synchronize') {
if (!authorized) { console.log(`🔒 Synchronize event detected - checking if Showtime is active`);
console.log(`🚨 Unauthorized user ${actor} pushed code - setting blocked label and bailing`);
// Set blocked label for security // Check if PR has any circus tent labels (Showtime is in use)
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: ['🎪 🔒 showtime-blocked']
});
core.setOutput('authorized', 'false');
return;
}
console.log(`✅ Authorized maintainer ${actor} - checking if Showtime is active`);
// Check if PR has any circus tent labels (Showtime is active)
const { data: issue } = await github.rest.issues.get({ const { data: issue } = await github.rest.issues.get({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
@@ -90,24 +75,30 @@ jobs:
const hasCircusLabels = issue.labels.some(label => label.name.startsWith('🎪 ')); const hasCircusLabels = issue.labels.some(label => label.name.startsWith('🎪 '));
if (hasCircusLabels) { if (hasCircusLabels) {
console.log(`🎪 Circus labels found - Showtime is active, proceeding with workflow`); console.log(`🎪 Circus labels found - setting blocked label to prevent auto-deployment`);
core.setOutput('authorized', 'true');
} else {
console.log(`️ No circus labels found - Showtime not active, skipping workflow`);
core.setOutput('authorized', 'false');
}
} else {
// Non-synchronize events - check authorization normally
if (!authorized) {
console.log(`🚨 Unauthorized user ${actor} - skipping all operations`);
core.setOutput('authorized', 'false');
return;
}
console.log(`✅ Authorized maintainer: ${actor}`); await github.rest.issues.addLabels({
core.setOutput('authorized', 'true'); owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: ['🎪 🔒 showtime-blocked']
});
console.log(`✅ Blocked label set - Showtime will detect and skip operations`);
} else {
console.log(`️ No circus labels found - Showtime not in use, skipping block`);
}
} }
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 - name: Install Superset Showtime
if: steps.auth.outputs.authorized == 'true' if: steps.auth.outputs.authorized == 'true'
run: | run: |
@@ -66,16 +66,23 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version-file: './superset-frontend/.nvmrc' node-version-file: './superset-frontend/.nvmrc'
- name: Setup Frontend Environment with builds and Cypress - name: Install npm dependencies
uses: ./.github/actions/setup-frontend/ uses: ./.github/actions/cached-dependencies
with: with:
build-instrumented: 'true' run: npm-install
install-cypress: 'true' - name: Build javascript packages
uses: ./.github/actions/cached-dependencies
with:
run: build-instrumented-assets
- name: Setup Postgres - name: Setup Postgres
if: steps.check.outcome == 'failure' if: steps.check.outcome == 'failure'
uses: ./.github/actions/cached-dependencies uses: ./.github/actions/cached-dependencies
with: with:
run: setup-postgres run: setup-postgres
- name: Install cypress
uses: ./.github/actions/cached-dependencies
with:
run: cypress-install
- name: Run Cypress - name: Run Cypress
uses: ./.github/actions/cached-dependencies uses: ./.github/actions/cached-dependencies
env: env:
@@ -43,8 +43,10 @@ jobs:
uses: ./.github/actions/cached-dependencies uses: ./.github/actions/cached-dependencies
with: with:
run: eyes-storybook-dependencies run: eyes-storybook-dependencies
- name: Setup Frontend Environment - name: Install NPM dependencies
uses: ./.github/actions/setup-frontend/ uses: ./.github/actions/cached-dependencies
with:
run: npm-install
- name: Run Applitools Eyes-Storybook - name: Run Applitools Eyes-Storybook
working-directory: ./superset-frontend working-directory: ./superset-frontend
run: npx eyes-storybook -u https://superset-storybook.netlify.app/ run: npx eyes-storybook -u https://superset-storybook.netlify.app/
+13 -4
View File
@@ -112,12 +112,21 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version-file: './superset-frontend/.nvmrc' node-version-file: './superset-frontend/.nvmrc'
- name: Setup Frontend Environment with builds - name: Install npm dependencies
if: steps.check.outputs.python || steps.check.outputs.frontend if: steps.check.outputs.python || steps.check.outputs.frontend
uses: ./.github/actions/setup-frontend/ uses: ./.github/actions/cached-dependencies
with: with:
build-instrumented: 'true' run: npm-install
install-cypress: 'true' - 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 cypress
if: steps.check.outputs.python || steps.check.outputs.frontend
uses: ./.github/actions/cached-dependencies
with:
run: cypress-install
- name: Run Cypress - name: Run Cypress
if: steps.check.outputs.python || steps.check.outputs.frontend if: steps.check.outputs.python || steps.check.outputs.frontend
uses: ./.github/actions/cached-dependencies uses: ./.github/actions/cached-dependencies
+1 -1
View File
@@ -138,7 +138,7 @@ jobs:
- name: eslint - name: eslint
run: | run: |
docker run --rm $TAG bash -c \ docker run --rm $TAG bash -c \
"npm ci && npm rebuild && npm run eslint -- . --quiet" "npm i && npm run eslint -- . --quiet"
- name: tsc - name: tsc
run: | run: |
+9 -2
View File
@@ -29,9 +29,16 @@ jobs:
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Frontend Environment - name: Setup Node.js
if: steps.check.outputs.frontend if: steps.check.outputs.frontend
uses: ./.github/actions/setup-frontend/ uses: actions/setup-node@v4
with:
node-version-file: './superset-frontend/.nvmrc'
- name: Install dependencies
if: steps.check.outputs.frontend
uses: ./.github/actions/cached-dependencies
with:
run: npm-install
- name: lint - name: lint
if: steps.check.outputs.frontend if: steps.check.outputs.frontend
working-directory: ./superset-frontend working-directory: ./superset-frontend
+10
View File
@@ -59,6 +59,11 @@ jobs:
install-docker-compose: "false" install-docker-compose: "false"
build: "true" build: "true"
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup supersetbot - name: Setup supersetbot
uses: ./.github/actions/setup-supersetbot/ uses: ./.github/actions/setup-supersetbot/
@@ -106,6 +111,11 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup supersetbot - name: Setup supersetbot
uses: ./.github/actions/setup-supersetbot/ uses: ./.github/actions/setup-supersetbot/
+8 -2
View File
@@ -29,8 +29,14 @@ jobs:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Setup Frontend Environment - name: Set up Node.js
uses: ./.github/actions/setup-frontend/ uses: actions/setup-node@v4
with:
node-version-file: './superset-frontend/.nvmrc'
- name: Install Dependencies
run: npm ci
working-directory: ./superset-frontend
- name: Run Script - name: Run Script
env: env:
-1
View File
@@ -83,7 +83,6 @@ intro_header.txt
# for LLMs # for LLMs
llm-context.md llm-context.md
LLMS.md LLMS.md
AGENTS.md
CLAUDE.md CLAUDE.md
CURSOR.md CURSOR.md
GEMINI.md GEMINI.md
-1
View File
@@ -1 +0,0 @@
LLMS.md
+4
View File
@@ -70,6 +70,10 @@ superset/
- **New files require ASF license headers** - When creating new code files, include the standard Apache Software Foundation license header - **New files require ASF license headers** - When creating new code files, include the standard Apache Software Foundation license header
- **LLM instruction files are excluded** - Files like LLMS.md, CLAUDE.md, etc. are in `.rat-excludes` to avoid header token overhead - **LLM instruction files are excluded** - Files like LLMS.md, CLAUDE.md, etc. are in `.rat-excludes` to avoid header token overhead
### Code Comments
- **Avoid time-specific language** - Don't use words like "now", "currently", "today" in code comments as they become outdated
- **Write timeless comments** - Comments should remain accurate regardless of when they're read
## Documentation Requirements ## Documentation Requirements
- **docs/**: Update for any user-facing changes - **docs/**: Update for any user-facing changes
+1
View File
@@ -28,6 +28,7 @@ x-superset-image: &superset-image apachesuperset.docker.scarf.sh/apache/superset
x-superset-volumes: x-superset-volumes:
&superset-volumes # /app/pythonpath_docker will be appended to the PYTHONPATH in the final container &superset-volumes # /app/pythonpath_docker will be appended to the PYTHONPATH in the final container
- ./docker:/app/docker - ./docker:/app/docker
- ./superset-core:/app/superset-core
- superset_home:/app/superset_home - superset_home:/app/superset_home
services: services:
+4
View File
@@ -29,9 +29,11 @@ x-superset-volumes: &superset-volumes
# /app/pythonpath_docker will be appended to the PYTHONPATH in the final container # /app/pythonpath_docker will be appended to the PYTHONPATH in the final container
- ./docker:/app/docker - ./docker:/app/docker
- ./superset:/app/superset - ./superset:/app/superset
- ./superset-core:/app/superset-core
- ./superset-frontend:/app/superset-frontend - ./superset-frontend:/app/superset-frontend
- superset_home:/app/superset_home - superset_home:/app/superset_home
- ./tests:/app/tests - ./tests:/app/tests
- superset_data:/app/data
x-common-build: &common-build x-common-build: &common-build
context: . context: .
target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean` target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean`
@@ -274,3 +276,5 @@ volumes:
external: false external: false
redis: redis:
external: false external: false
superset_data:
external: false
+11 -3
View File
@@ -21,8 +21,15 @@ set -eo pipefail
# Make python interactive # Make python interactive
if [ "$DEV_MODE" == "true" ]; then if [ "$DEV_MODE" == "true" ]; then
if [ "$(whoami)" = "root" ] && command -v uv > /dev/null 2>&1; then if [ "$(whoami)" = "root" ] && command -v uv > /dev/null 2>&1; then
echo "Reinstalling the app in editable mode" # Always ensure superset-core is available
uv pip install -e . echo "Installing superset-core in editable mode"
uv pip install --no-deps -e /app/superset-core
# Only reinstall the main app for non-worker processes
if [ "$1" != "worker" ] && [ "$1" != "beat" ]; then
echo "Reinstalling the app in editable mode"
uv pip install -e .
fi
fi fi
fi fi
REQUIREMENTS_LOCAL="/app/docker/requirements-local.txt" REQUIREMENTS_LOCAL="/app/docker/requirements-local.txt"
@@ -34,7 +41,8 @@ if [ "$CYPRESS_CONFIG" == "true" ]; then
export SUPERSET__SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://superset:superset@db:5432/superset_cypress export SUPERSET__SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://superset:superset@db:5432/superset_cypress
PORT=8081 PORT=8081
fi fi
if [[ "$DATABASE_DIALECT" == postgres* ]] && [ "$(whoami)" = "root" ]; then # Skip postgres requirements installation for workers to avoid conflicts
if [[ "$DATABASE_DIALECT" == postgres* ]] && [ "$(whoami)" = "root" ] && [ "$1" != "worker" ] && [ "$1" != "beat" ]; then
# older images may not have the postgres dev requirements installed # older images may not have the postgres dev requirements installed
echo "Installing postgres requirements" echo "Installing postgres requirements"
if command -v uv > /dev/null 2>&1; then if command -v uv > /dev/null 2>&1; then
+2 -2
View File
@@ -36,11 +36,11 @@ Screenshots will be taken but no messages actually sent as long as `ALERT_REPORT
#### In your `Dockerfile` #### In your `Dockerfile`
You'll need to extend the Superset image to include a headless browser. Your options include: 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 Playwright with Chrome: this is the recommended approach as of version 4.1.x or greater. 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 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`. - Use Chrome without Playwright: you'll need to install Chrome and set 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. In Superset versions prior to 4.1, users installed Firefox or Chrome and that was documented here.
Only the worker container needs the browser. Only the worker container needs the browser.
+1 -1
View File
@@ -67,7 +67,7 @@ are compatible with Superset.
| [IBM Netezza Performance Server](/docs/configuration/databases#ibm-netezza-performance-server) | `pip install nzalchemy` | `netezza+nzpy://<UserName>:<DBPassword>@<Database Host>/<Database Name>` | | [IBM Netezza Performance Server](/docs/configuration/databases#ibm-netezza-performance-server) | `pip install nzalchemy` | `netezza+nzpy://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
| [MySQL](/docs/configuration/databases#mysql) | `pip install mysqlclient` | `mysql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` | | [MySQL](/docs/configuration/databases#mysql) | `pip install mysqlclient` | `mysql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
| [OceanBase](/docs/configuration/databases#oceanbase) | `pip install oceanbase_py` | `oceanbase://<UserName>:<DBPassword>@<Database Host>/<Database Name>` | | [OceanBase](/docs/configuration/databases#oceanbase) | `pip install oceanbase_py` | `oceanbase://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
| [Oracle](/docs/configuration/databases#oracle) | `pip install cx_Oracle` | `oracle://<username>:<password>@<hostname>:<port>` | | [Oracle](/docs/configuration/databases#oracle) | `pip install oracledb` | `oracle://<username>:<password>@<hostname>:<port>` |
| [Parseable](/docs/configuration/databases#parseable) | `pip install sqlalchemy-parseable` | `parseable://<UserName>:<DBPassword>@<Database Host>/<Stream Name>` | | [Parseable](/docs/configuration/databases#parseable) | `pip install sqlalchemy-parseable` | `parseable://<UserName>:<DBPassword>@<Database Host>/<Stream Name>` |
| [PostgreSQL](/docs/configuration/databases#postgres) | `pip install psycopg2` | `postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` | | [PostgreSQL](/docs/configuration/databases#postgres) | `pip install psycopg2` | `postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
| [Presto](/docs/configuration/databases#presto) | `pip install pyhive` | `presto://{username}:{password}@{hostname}:{port}/{database}` | | [Presto](/docs/configuration/databases#presto) | `pip install pyhive` | `presto://{username}:{password}@{hostname}:{port}/{database}` |
+1 -1
View File
@@ -7,7 +7,7 @@ version: 1
# Theming Superset # Theming Superset
:::note :::note
apache-superset>=6.0 `apache-superset>=6.0`
::: :::
Superset now rides on **Ant Design v5's token-based theming**. Superset now rides on **Ant Design v5's token-based theming**.
+1 -1
View File
@@ -130,7 +130,7 @@ Committers may also update title to reflect the issue/PR content if the author-p
If the PR passes CI tests and does not have any `need:` labels, it is ready for review, add label `review` and/or `design-review`. If the PR passes CI tests and does not have any `need:` labels, it is ready for review, add label `review` and/or `design-review`.
If an issue/PR has been inactive for >=30 days, it will be closed. If it does not have any status label, add `inactive`. If an issue/PR has been inactive for at least 30 days, it will be closed. If it does not have any status label, add `inactive`.
When creating a PR, if you're aiming to have it included in a specific release, please tag it with the version label. For example, to have a PR considered for inclusion in Superset 1.1 use the label `v1.1`. When creating a PR, if you're aiming to have it included in a specific release, please tag it with the version label. For example, to have a PR considered for inclusion in Superset 1.1 use the label `v1.1`.
+1 -1
View File
@@ -29,7 +29,7 @@ maintainers:
- name: craig-rueda - name: craig-rueda
email: craig@craigrueda.com email: craig@craigrueda.com
url: https://github.com/craig-rueda url: https://github.com/craig-rueda
version: 0.15.0 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details. version: 0.15.1 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
dependencies: dependencies:
- name: postgresql - name: postgresql
version: 13.4.4 version: 13.4.4
+2 -1
View File
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
# superset # superset
![Version: 0.15.0](https://img.shields.io/badge/Version-0.15.0-informational?style=flat-square) ![Version: 0.15.1](https://img.shields.io/badge/Version-0.15.1-informational?style=flat-square)
Apache Superset is a modern, enterprise-ready business intelligence web application Apache Superset is a modern, enterprise-ready business intelligence web application
@@ -203,6 +203,7 @@ On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverri
| supersetNode.connections.db_name | string | `"superset"` | | | supersetNode.connections.db_name | string | `"superset"` | |
| supersetNode.connections.db_pass | string | `"superset"` | | | supersetNode.connections.db_pass | string | `"superset"` | |
| supersetNode.connections.db_port | string | `"5432"` | | | supersetNode.connections.db_port | string | `"5432"` | |
| supersetNode.connections.db_type | string | `"postgresql"` | Database type for Superset metadata (Supported types: "postgresql", "mysql") |
| supersetNode.connections.db_user | string | `"superset"` | | | supersetNode.connections.db_user | string | `"superset"` | |
| supersetNode.connections.redis_cache_db | string | `"1"` | | | supersetNode.connections.redis_cache_db | string | `"1"` | |
| supersetNode.connections.redis_celery_db | string | `"0"` | | | supersetNode.connections.redis_celery_db | string | `"0"` | |
+12 -1
View File
@@ -96,7 +96,18 @@ CACHE_CONFIG = {
} }
DATA_CACHE_CONFIG = CACHE_CONFIG DATA_CACHE_CONFIG = CACHE_CONFIG
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{env('DB_USER')}:{env('DB_PASS')}@{env('DB_HOST')}:{env('DB_PORT')}/{env('DB_NAME')}"
if os.getenv("SQLALCHEMY_DATABASE_URI"):
SQLALCHEMY_DATABASE_URI = os.getenv("SQLALCHEMY_DATABASE_URI")
else:
{{- if eq .Values.supersetNode.connections.db_type "postgresql" }}
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
{{- else if eq .Values.supersetNode.connections.db_type "mysql" }}
SQLALCHEMY_DATABASE_URI = f"mysql+mysqldb://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
{{- else }}
{{ fail (printf "Unsupported database type: %s. Please use 'postgresql' or 'mysql'." .Values.supersetNode.connections.db_type) }}
{{- end }}
SQLALCHEMY_TRACK_MODIFICATIONS = True SQLALCHEMY_TRACK_MODIFICATIONS = True
class CeleryConfig: class CeleryConfig:
+2
View File
@@ -289,6 +289,8 @@ supersetNode:
enabled: false enabled: false
ssl_cert_reqs: CERT_NONE ssl_cert_reqs: CERT_NONE
# You need to change below configuration incase bringing own PostgresSQL instance and also set postgresql.enabled:false # You need to change below configuration incase bringing own PostgresSQL instance and also set postgresql.enabled:false
# -- Database type for Superset metadata (Supported types: "postgresql", "mysql")
db_type: "postgresql"
db_host: "{{ .Release.Name }}-postgresql" db_host: "{{ .Release.Name }}-postgresql"
db_port: "5432" db_port: "5432"
db_user: superset db_user: superset
+1 -1
View File
@@ -100,7 +100,7 @@ dependencies = [
"slack_sdk>=3.19.0, <4", "slack_sdk>=3.19.0, <4",
"sqlalchemy>=1.4, <2", "sqlalchemy>=1.4, <2",
"sqlalchemy-utils>=0.38.3, <0.39", "sqlalchemy-utils>=0.38.3, <0.39",
"sqlglot>=27.3.0, <28", "sqlglot>=27.15.2, <28",
# newer pandas needs 0.9+ # newer pandas needs 0.9+
"tabulate>=0.9.0, <1.0", "tabulate>=0.9.0, <1.0",
"typing-extensions>=4, <5", "typing-extensions>=4, <5",
+1 -1
View File
@@ -395,7 +395,7 @@ sqlalchemy-utils==0.38.3
# via # via
# apache-superset (pyproject.toml) # apache-superset (pyproject.toml)
# flask-appbuilder # flask-appbuilder
sqlglot==27.3.0 sqlglot==27.15.2
# via apache-superset (pyproject.toml) # via apache-superset (pyproject.toml)
sshtunnel==0.4.0 sshtunnel==0.4.0
# via apache-superset (pyproject.toml) # via apache-superset (pyproject.toml)
+1 -1
View File
@@ -848,7 +848,7 @@ sqlalchemy-utils==0.38.3
# -c requirements/base-constraint.txt # -c requirements/base-constraint.txt
# apache-superset # apache-superset
# flask-appbuilder # flask-appbuilder
sqlglot==27.3.0 sqlglot==27.15.2
# via # via
# -c requirements/base-constraint.txt # -c requirements/base-constraint.txt
# apache-superset # apache-superset
+5 -12
View File
@@ -83,6 +83,7 @@ module.exports = {
'plugin:react-hooks/recommended', 'plugin:react-hooks/recommended',
'plugin:react-prefer-function-component/recommended', 'plugin:react-prefer-function-component/recommended',
'plugin:storybook/recommended', 'plugin:storybook/recommended',
'plugin:react-you-might-not-need-an-effect/legacy-recommended',
], ],
parser: '@babel/eslint-parser', parser: '@babel/eslint-parser',
parserOptions: { parserOptions: {
@@ -121,7 +122,7 @@ module.exports = {
'lodash', 'lodash',
'theme-colors', 'theme-colors',
'icons', 'icons',
'superset-i18n', 'i18n-strings',
'react-prefer-function-component', 'react-prefer-function-component',
'prettier', 'prettier',
], ],
@@ -177,7 +178,6 @@ module.exports = {
'.json': 'always', '.json': 'always',
}, },
], ],
'import/no-named-as-default': 0,
'import/no-named-as-default-member': 0, 'import/no-named-as-default-member': 0,
'import/prefer-default-export': 0, 'import/prefer-default-export': 0,
indent: 0, indent: 0,
@@ -394,7 +394,7 @@ module.exports = {
rules: { rules: {
'theme-colors/no-literal-colors': 0, 'theme-colors/no-literal-colors': 0,
'icons/no-fa-icons-usage': 0, 'icons/no-fa-icons-usage': 0,
'superset-i18n/no-template-vars': 0, 'i18n-strings/no-template-vars': 0,
'no-restricted-imports': 0, 'no-restricted-imports': 0,
'react/no-void-elements': 0, 'react/no-void-elements': 0,
}, },
@@ -411,15 +411,8 @@ module.exports = {
rules: { rules: {
'theme-colors/no-literal-colors': 'error', 'theme-colors/no-literal-colors': 'error',
'icons/no-fa-icons-usage': 'error', 'icons/no-fa-icons-usage': 'error',
'superset-i18n/no-template-vars': ['error', true], 'i18n-strings/no-template-vars': ['error', true],
'superset-i18n/sentence-case-buttons': 'error', 'i18n-strings/sentence-case-buttons': 'error',
camelcase: [
'error',
{
allow: ['^UNSAFE_'],
properties: 'never',
},
],
'class-methods-use-this': 0, 'class-methods-use-this': 0,
curly: 2, curly: 2,
'func-names': 0, 'func-names': 0,
+1
View File
@@ -3,3 +3,4 @@ cypress/screenshots
cypress/videos cypress/videos
src/temp src/temp
.temp_cache/ .temp_cache/
.tsbuildinfo
-10
View File
@@ -46,16 +46,6 @@ module.exports = {
resolve: { resolve: {
...config.resolve, ...config.resolve,
...customConfig.resolve, ...customConfig.resolve,
alias: {
...config.resolve?.alias,
...customConfig.resolve?.alias,
'react-dom/test-utils': require.resolve('react-dom/test-utils.js'),
},
extensionAlias: {
'.js': ['.js', '.ts', '.tsx'],
'.mjs': ['.mjs', '.mts'],
},
fullySpecified: false,
}, },
plugins: [...config.plugins, ...customConfig.plugins], plugins: [...config.plugins, ...customConfig.plugins],
}), }),
+2 -1
View File
@@ -44,6 +44,7 @@ module.exports = {
'@babel/preset-typescript', '@babel/preset-typescript',
], ],
plugins: [ plugins: [
'lodash',
'@babel/plugin-syntax-dynamic-import', '@babel/plugin-syntax-dynamic-import',
'@babel/plugin-transform-export-namespace-from', '@babel/plugin-transform-export-namespace-from',
['@babel/plugin-transform-class-properties', { loose: true }], ['@babel/plugin-transform-class-properties', { loose: true }],
@@ -96,7 +97,7 @@ module.exports = {
instrumented: { instrumented: {
plugins: [ plugins: [
[ [
'babel-plugin-istanbul', 'istanbul',
{ {
exclude: ['plugins/**/*', 'packages/**/*'], exclude: ['plugins/**/*', 'packages/**/*'],
}, },
@@ -1,5 +1,5 @@
{ {
"name": "eslint-plugin-superset-i18n", "name": "eslint-plugin-i18n-strings",
"version": "1.0.0", "version": "1.0.0",
"description": "Warns about translation variables", "description": "Warns about translation variables",
"main": "index.js", "main": "index.js",
+1 -15
View File
@@ -35,16 +35,13 @@ module.exports = {
'^@apache-superset/core$': '<rootDir>/packages/superset-core/src', '^@apache-superset/core$': '<rootDir>/packages/superset-core/src',
'^@apache-superset/core/(.*)$': '<rootDir>/packages/superset-core/src/$1', '^@apache-superset/core/(.*)$': '<rootDir>/packages/superset-core/src/$1',
}, },
testEnvironment: 'jest-fixed-jsdom', testEnvironment: '<rootDir>/spec/helpers/jsDomWithFetchAPI.ts',
modulePathIgnorePatterns: ['<rootDir>/packages/generator-superset'], modulePathIgnorePatterns: ['<rootDir>/packages/generator-superset'],
setupFilesAfterEnv: ['<rootDir>/spec/helpers/setup.ts'], setupFilesAfterEnv: ['<rootDir>/spec/helpers/setup.ts'],
snapshotSerializers: ['@emotion/jest/serializer'], snapshotSerializers: ['@emotion/jest/serializer'],
testEnvironmentOptions: { testEnvironmentOptions: {
globalsCleanup: true, globalsCleanup: true,
url: 'http://localhost', url: 'http://localhost',
// Jest 30 compatibility: Ensure proper cleanup
resources: 'usable',
runScripts: 'dangerously',
}, },
collectCoverageFrom: [ collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}', 'src/**/*.{js,jsx,ts,tsx}',
@@ -83,15 +80,4 @@ module.exports = {
], ],
], ],
testTimeout: 20000, testTimeout: 20000,
// Jest 30 compatibility: Handle timers and async operations properly
fakeTimers: {
enableGlobally: false,
legacyFakeTimers: false,
},
// Better cleanup for worker processes
detectOpenHandles: false,
forceExit: true,
// Improved memory management
maxWorkers: '80%',
workerIdleMemoryLimit: '512MB',
}; };
+5722 -1354
View File
File diff suppressed because it is too large Load Diff
+20 -58
View File
@@ -40,7 +40,7 @@
"_prettier": "prettier './({src,spec,cypress-base,plugins,packages,.storybook}/**/*{.js,.jsx,.ts,.tsx,.css,.scss,.sass}|package.json)'", "_prettier": "prettier './({src,spec,cypress-base,plugins,packages,.storybook}/**/*{.js,.jsx,.ts,.tsx,.css,.scss,.sass}|package.json)'",
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production BABEL_ENV=\"${BABEL_ENV:=production}\" webpack --color --mode production", "build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production BABEL_ENV=\"${BABEL_ENV:=production}\" webpack --color --mode production",
"build-dev": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=development webpack --mode=development --color", "build-dev": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=development webpack --mode=development --color",
"build-instrumented": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production BABEL_ENV=instrumented webpack --mode=production --color", "build-instrumented": "cross-env NODE_ENV=production BABEL_ENV=instrumented webpack --mode=production --color",
"build-storybook": "storybook build", "build-storybook": "storybook build",
"build-translation": "scripts/po2json.sh", "build-translation": "scripts/po2json.sh",
"bundle-stats": "cross-env BUNDLE_ANALYZER=true npm run build && npx open-cli ../superset/static/stats/statistics.html", "bundle-stats": "cross-env BUNDLE_ANALYZER=true npm run build && npx open-cli ../superset/static/stats/statistics.html",
@@ -133,15 +133,13 @@
"chrono-node": "^2.7.8", "chrono-node": "^2.7.8",
"classnames": "^2.2.5", "classnames": "^2.2.5",
"content-disposition": "^0.5.4", "content-disposition": "^0.5.4",
"currencyformatter.js": "^2.2.0",
"d3-color": "^3.1.0", "d3-color": "^3.1.0",
"d3-scale": "^4.0.2", "d3-scale": "^2.1.2",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"dom-to-image-more": "^3.6.0", "dom-to-image-more": "^3.6.0",
"dom-to-pdf": "^0.3.2", "dom-to-pdf": "^0.3.2",
"echarts": "^5.6.0", "echarts": "^5.6.0",
"emotion-rgba": "0.0.12", "eslint-plugin-i18n-strings": "file:eslint-rules/eslint-plugin-i18n-strings",
"eslint-plugin-superset-i18n": "file:eslint-rules/eslint-plugin-i18n-strings",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
@@ -152,7 +150,6 @@
"geostyler-qgis-parser": "2.0.1", "geostyler-qgis-parser": "2.0.1",
"geostyler-style": "7.5.0", "geostyler-style": "7.5.0",
"geostyler-wfs-parser": "^2.0.3", "geostyler-wfs-parser": "^2.0.3",
"global-box": "^2.0.2",
"googleapis": "^154.1.0", "googleapis": "^154.1.0",
"immer": "^10.1.1", "immer": "^10.1.1",
"interweave": "^13.1.0", "interweave": "^13.1.0",
@@ -173,7 +170,6 @@
"ol": "^7.5.2", "ol": "^7.5.2",
"polished": "^4.3.1", "polished": "^4.3.1",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"query-string": "^7.1.3",
"re-resizable": "^6.10.1", "re-resizable": "^6.10.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-checkbox-tree": "^1.8.0", "react-checkbox-tree": "^1.8.0",
@@ -194,10 +190,9 @@
"react-search-input": "^0.11.3", "react-search-input": "^0.11.3",
"react-sortable-hoc": "^2.0.0", "react-sortable-hoc": "^2.0.0",
"react-split": "^2.0.9", "react-split": "^2.0.9",
"react-syntax-highlighter": "^15.6.6",
"react-table": "^7.8.0", "react-table": "^7.8.0",
"react-transition-group": "^4.4.5", "react-transition-group": "^4.4.5",
"react-virtualized-auto-sizer": "^1.0.25", "react-virtualized-auto-sizer": "^1.0.26",
"react-window": "^1.8.10", "react-window": "^1.8.10",
"redux": "^4.2.1", "redux": "^4.2.1",
"redux-localstorage": "^0.4.1", "redux-localstorage": "^0.4.1",
@@ -211,7 +206,7 @@
"urijs": "^1.19.8", "urijs": "^1.19.8",
"use-event-callback": "^0.1.0", "use-event-callback": "^0.1.0",
"use-immer": "^0.9.0", "use-immer": "^0.9.0",
"use-query-params": "^1.2.3", "use-query-params": "^1.1.9",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
@@ -240,15 +235,15 @@
"@istanbuljs/nyc-config-typescript": "^1.0.1", "@istanbuljs/nyc-config-typescript": "^1.0.1",
"@mihkeleidast/storybook-addon-source": "^1.0.1", "@mihkeleidast/storybook-addon-source": "^1.0.1",
"@playwright/test": "^1.49.1", "@playwright/test": "^1.49.1",
"@storybook/addon-actions": "8.6.14", "@storybook/addon-actions": "8.1.11",
"@storybook/addon-controls": "8.6.14", "@storybook/addon-controls": "8.1.11",
"@storybook/addon-essentials": "8.6.14", "@storybook/addon-essentials": "8.1.11",
"@storybook/addon-links": "8.6.14", "@storybook/addon-links": "8.1.11",
"@storybook/addon-mdx-gfm": "8.6.14", "@storybook/addon-mdx-gfm": "8.1.11",
"@storybook/components": "8.6.14", "@storybook/components": "8.1.11",
"@storybook/preview-api": "8.6.14", "@storybook/preview-api": "8.1.11",
"@storybook/react": "8.6.14", "@storybook/react": "8.1.11",
"@storybook/react-webpack5": "8.6.14", "@storybook/react-webpack5": "8.1.11",
"@svgr/webpack": "^8.1.0", "@svgr/webpack": "^8.1.0",
"@testing-library/dom": "^8.20.1", "@testing-library/dom": "^8.20.1",
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.3",
@@ -272,7 +267,6 @@
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"@types/react-transition-group": "^4.4.12", "@types/react-transition-group": "^4.4.12",
"@types/react-virtualized-auto-sizer": "^1.0.8", "@types/react-virtualized-auto-sizer": "^1.0.8",
"@types/react-ultimate-pagination": "^1.2.4",
"@types/react-window": "^1.8.8", "@types/react-window": "^1.8.8",
"@types/redux-localstorage": "^1.0.8", "@types/redux-localstorage": "^1.0.8",
"@types/redux-mock-store": "^1.0.6", "@types/redux-mock-store": "^1.0.6",
@@ -286,6 +280,7 @@
"babel-loader": "^10.0.0", "babel-loader": "^10.0.0",
"babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-jsx-remove-data-test-id": "^3.0.0", "babel-plugin-jsx-remove-data-test-id": "^3.0.0",
"babel-plugin-lodash": "^3.3.4",
"babel-plugin-typescript-to-proptypes": "^2.0.0", "babel-plugin-typescript-to-proptypes": "^2.0.0",
"cheerio": "1.1.0", "cheerio": "1.1.0",
"copy-webpack-plugin": "^13.0.0", "copy-webpack-plugin": "^13.0.0",
@@ -310,6 +305,7 @@
"eslint-plugin-react": "^7.37.2", "eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-prefer-function-component": "^3.3.0", "eslint-plugin-react-prefer-function-component": "^3.3.0",
"eslint-plugin-react-you-might-not-need-an-effect": "^0.5.1",
"eslint-plugin-storybook": "^0.8.0", "eslint-plugin-storybook": "^0.8.0",
"eslint-plugin-testing-library": "^6.4.0", "eslint-plugin-testing-library": "^6.4.0",
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors", "eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
@@ -319,8 +315,7 @@
"html-webpack-plugin": "^5.6.3", "html-webpack-plugin": "^5.6.3",
"imports-loader": "^5.0.0", "imports-loader": "^5.0.0",
"jest": "^30.0.2", "jest": "^30.0.2",
"jest-environment-jsdom": "^30.0.3", "jest-environment-jsdom": "^29.7.0",
"jest-fixed-jsdom": "^0.0.10",
"jest-html-reporter": "^4.3.0", "jest-html-reporter": "^4.3.0",
"jest-websocket-mock": "^2.5.0", "jest-websocket-mock": "^2.5.0",
"jsdom": "^26.0.0", "jsdom": "^26.0.0",
@@ -337,7 +332,7 @@
"source-map": "^0.7.4", "source-map": "^0.7.4",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"speed-measure-webpack-plugin": "^1.5.0", "speed-measure-webpack-plugin": "^1.5.0",
"storybook": "8.6.14", "storybook": "8.1.11",
"style-loader": "^4.0.0", "style-loader": "^4.0.0",
"thread-loader": "^4.0.4", "thread-loader": "^4.0.4",
"ts-jest": "^29.4.0", "ts-jest": "^29.4.0",
@@ -366,45 +361,12 @@
"npm": "^10.8.1" "npm": "^10.8.1"
}, },
"overrides": { "overrides": {
"@superset-ui/legacy-plugin-chart-horizon": {
"d3-color": "^3.1.0",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2"
},
"@superset-ui/legacy-preset-chart-deckgl": {
"d3-color": "^3.1.0",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2"
},
"@superset-ui/plugin-chart-word-cloud": {
"d3-color": "^3.1.0",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2"
},
"core-js": "^3.38.1", "core-js": "^3.38.1",
"d3-color": "^3.1.0", "d3-color": "^3.1.0",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-scale-chromatic": "^3.1.0",
"encodable": "^0.5.4",
"glob": "^11.0.0",
"babel-plugin-istanbul": "^6.1.1",
"test-exclude": "^7.0.1",
"jspdf": "^3.0.1",
"nwsapi": "^2.2.13",
"prismjs": "^1.30.0",
"puppeteer": "^22.4.1", "puppeteer": "^22.4.1",
"rimraf": "^6.0.0",
"tr46": {
"punycode": "^2.3.1"
},
"underscore": "^1.13.7", "underscore": "^1.13.7",
"handlebars": "^4.7.8", "jspdf": "^3.0.1",
"@storybook/core": "8.6.14", "nwsapi": "^2.2.13"
"storybook": "8.6.14",
"whatwg-url": {
"punycode": "^2.3.1"
}
}, },
"readme": "ERROR: No README data found!", "readme": "ERROR: No README data found!",
"scarfSettings": { "scarfSettings": {
@@ -1,6 +1,6 @@
{ {
"name": "@apache-superset/core", "name": "@apache-superset/core",
"version": "0.0.1-rc4", "version": "0.0.1-rc5",
"description": "This package contains UI elements, APIs, and utility functions used by Superset.", "description": "This package contains UI elements, APIs, and utility functions used by Superset.",
"sideEffects": false, "sideEffects": false,
"main": "lib/index.js", "main": "lib/index.js",
@@ -1,8 +1,15 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..", "baseUrl": "../..",
"outDir": "lib"
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
}, },
"include": ["src/**/*", "types/**/*"], "include": ["src/**/*", "types/**/*"],
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"] "exclude": ["src/**/*.test.*", "src/**/*.stories.*"]
@@ -18,7 +18,8 @@
* under the License. * under the License.
*/ */
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { css, GenericDataType, styled, t } from '@superset-ui/core'; import { css, styled, t } from '@superset-ui/core';
import { GenericDataType } from '@apache-superset/core/api/core';
import { import {
ClockCircleOutlined, ClockCircleOutlined,
QuestionOutlined, QuestionOutlined,
@@ -16,13 +16,8 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { import { DTTM_ALIAS, QueryColumn, QueryMode, t } from '@superset-ui/core';
DTTM_ALIAS, import { GenericDataType } from '@apache-superset/core/api/core';
GenericDataType,
QueryColumn,
QueryMode,
t,
} from '@superset-ui/core';
import { ColumnMeta, SortSeriesData, SortSeriesType } from './types'; import { ColumnMeta, SortSeriesData, SortSeriesType } from './types';
export const DEFAULT_MAX_ROW = 100000; export const DEFAULT_MAX_ROW = 100000;
@@ -16,7 +16,8 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { DatasourceType, GenericDataType } from '@superset-ui/core'; import { DatasourceType } from '@superset-ui/core';
import { GenericDataType } from '@apache-superset/core/api/core';
import { Dataset } from './types'; import { Dataset } from './types';
export const TestDataset: Dataset = { export const TestDataset: Dataset = {
@@ -20,13 +20,13 @@
import { import {
ContributionType, ContributionType,
ensureIsArray, ensureIsArray,
GenericDataType,
getColumnLabel, getColumnLabel,
getMetricLabel, getMetricLabel,
QueryFormColumn, QueryFormColumn,
QueryFormMetric, QueryFormMetric,
t, t,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { GenericDataType } from '@apache-superset/core/api/core';
import { import {
ControlPanelState, ControlPanelState,
ControlState, ControlState,
@@ -17,12 +17,8 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { import { QueryColumn, t, validateNonEmpty } from '@superset-ui/core';
GenericDataType, import { GenericDataType } from '@apache-superset/core/api/core';
QueryColumn,
t,
validateNonEmpty,
} from '@superset-ui/core';
import { import {
ExtraControlProps, ExtraControlProps,
SharedControlConfig, SharedControlConfig,
@@ -16,7 +16,8 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { ensureIsArray, GenericDataType, ValueOf } from '@superset-ui/core'; import { ensureIsArray, ValueOf } from '@superset-ui/core';
import { GenericDataType } from '@apache-superset/core/api/core';
import { ControlPanelState, isDataset, isQueryResponse } from '../types'; import { ControlPanelState, isDataset, isQueryResponse } from '../types';
export function checkColumnType( export function checkColumnType(
@@ -16,7 +16,8 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { GenericDataType, QueryColumn, QueryResponse } from '@superset-ui/core'; import { QueryColumn, QueryResponse } from '@superset-ui/core';
import { GenericDataType } from '@apache-superset/core/api/core';
import { ColumnMeta, Dataset, isDataset, isQueryResponse } from '../types'; import { ColumnMeta, Dataset, isDataset, isQueryResponse } from '../types';
export function columnsByType( export function columnsByType(
@@ -17,11 +17,11 @@
* under the License. * under the License.
*/ */
import { import {
GenericDataType,
getColumnLabel, getColumnLabel,
isPhysicalColumn, isPhysicalColumn,
QueryFormColumn, QueryFormColumn,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { GenericDataType } from '@apache-superset/core/api/core';
import { checkColumnType, ControlStateMapping } from '..'; import { checkColumnType, ControlStateMapping } from '..';
export function isSortable(controls: ControlStateMapping): boolean { export function isSortable(controls: ControlStateMapping): boolean {
@@ -18,8 +18,7 @@
*/ */
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { render } from '@superset-ui/core/spec'; import { render } from '@superset-ui/core/spec';
import { GenericDataType } from '@superset-ui/core'; import { GenericDataType } from '@apache-superset/core/api/core';
import { ColumnOption, ColumnOptionProps } from '../../src'; import { ColumnOption, ColumnOptionProps } from '../../src';
jest.mock('@superset-ui/chart-controls/components/SQLPopover', () => ({ jest.mock('@superset-ui/chart-controls/components/SQLPopover', () => ({
@@ -19,8 +19,7 @@
import { isValidElement } from 'react'; import { isValidElement } from 'react';
import { render, screen } from '@superset-ui/core/spec'; import { render, screen } from '@superset-ui/core/spec';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { GenericDataType } from '@superset-ui/core'; import { GenericDataType } from '@apache-superset/core/api/core';
import { ColumnTypeLabel, ColumnTypeLabelProps } from '../../src'; import { ColumnTypeLabel, ColumnTypeLabelProps } from '../../src';
describe('ColumnOption', () => { describe('ColumnOption', () => {
@@ -16,7 +16,8 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { GenericDataType, testQueryResponse } from '@superset-ui/core'; import { testQueryResponse } from '@superset-ui/core';
import { GenericDataType } from '@apache-superset/core/api/core';
import { checkColumnType, TestDataset } from '../../src'; import { checkColumnType, TestDataset } from '../../src';
test('checkColumnType columns from a Dataset', () => { test('checkColumnType columns from a Dataset', () => {
@@ -16,11 +16,8 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { import { DatasourceType, testQueryResponse } from '@superset-ui/core';
DatasourceType, import { GenericDataType } from '@apache-superset/core/api/core';
GenericDataType,
testQueryResponse,
} from '@superset-ui/core';
import { columnChoices } from '../../src'; import { columnChoices } from '../../src';
describe('columnChoices()', () => { describe('columnChoices()', () => {
@@ -16,11 +16,8 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { import { testQueryResponse, testQueryResults } from '@superset-ui/core';
GenericDataType, import { GenericDataType } from '@apache-superset/core/api/core';
testQueryResponse,
testQueryResults,
} from '@superset-ui/core';
import { import {
Dataset, Dataset,
getTemporalColumns, getTemporalColumns,
@@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import { ControlStateMapping } from '@superset-ui/chart-controls'; import { ControlStateMapping } from '@superset-ui/chart-controls';
import { GenericDataType } from '@superset-ui/core'; import { GenericDataType } from '@apache-superset/core/api/core';
import { isSortable } from '../../src/utils/isSortable'; import { isSortable } from '../../src/utils/isSortable';
const controls: ControlStateMapping = { const controls: ControlStateMapping = {
@@ -1,8 +1,15 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..", "baseUrl": "../..",
"outDir": "lib"
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
}, },
"include": ["src/**/*", "types/**/*"], "include": ["src/**/*", "types/**/*"],
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"], "exclude": ["src/**/*.test.*", "src/**/*.stories.*"],
@@ -16,5 +16,4 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
type FlashMessageType = 'info' | 'alert' | 'danger' | 'warning' | 'success'; module.exports = {};
export type FlashMessage = [FlashMessageType, string];
@@ -0,0 +1,19 @@
/**
* 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.
*/
module.exports = 'test-file-stub';
@@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SVGProps, forwardRef } from 'react';
const SvgrMock = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(
(props, ref) => <svg ref={ref} {...props} />,
);
SvgrMock.displayName = 'SvgrMock';
export const ReactComponent = SvgrMock;
export default SvgrMock;
@@ -35,78 +35,57 @@ const selector = '[id="ace-editor"]';
test('renders SQLEditor', async () => { test('renders SQLEditor', async () => {
const { container } = render(<SQLEditor />); const { container } = render(<SQLEditor />);
await waitFor( await waitFor(() => {
() => { expect(container.querySelector(selector)).toBeInTheDocument();
expect(container.querySelector(selector)).toBeInTheDocument(); });
},
{ timeout: 5000 },
);
}); });
test('renders FullSQLEditor', async () => { test('renders FullSQLEditor', async () => {
const { container } = render(<FullSQLEditor />); const { container } = render(<FullSQLEditor />);
await waitFor( await waitFor(() => {
() => { expect(container.querySelector(selector)).toBeInTheDocument();
expect(container.querySelector(selector)).toBeInTheDocument(); });
},
{ timeout: 5000 },
);
}); });
test('renders MarkdownEditor', async () => { test('renders MarkdownEditor', async () => {
const { container } = render(<MarkdownEditor />); const { container } = render(<MarkdownEditor />);
await waitFor( await waitFor(() => {
() => { expect(container.querySelector(selector)).toBeInTheDocument();
expect(container.querySelector(selector)).toBeInTheDocument(); });
},
{ timeout: 5000 },
);
}); });
test('renders TextAreaEditor', async () => { test('renders TextAreaEditor', async () => {
const { container } = render(<TextAreaEditor />); const { container } = render(<TextAreaEditor />);
await waitFor( await waitFor(() => {
() => { expect(container.querySelector(selector)).toBeInTheDocument();
expect(container.querySelector(selector)).toBeInTheDocument(); });
},
{ timeout: 5000 },
);
}); });
test('renders CssEditor', async () => { test('renders CssEditor', async () => {
const { container } = render(<CssEditor />); const { container } = render(<CssEditor />);
await waitFor( await waitFor(() => {
() => { expect(container.querySelector(selector)).toBeInTheDocument();
expect(container.querySelector(selector)).toBeInTheDocument(); });
},
{ timeout: 5000 },
);
}); });
test('renders JsonEditor', async () => { test('renders JsonEditor', async () => {
const { container } = render(<JsonEditor />); const { container } = render(<JsonEditor />);
await waitFor( await waitFor(() => {
() => { expect(container.querySelector(selector)).toBeInTheDocument();
expect(container.querySelector(selector)).toBeInTheDocument(); });
},
{ timeout: 5000 },
);
}); });
test('renders ConfigEditor', async () => { test('renders ConfigEditor', async () => {
const { container } = render(<ConfigEditor />); const { container } = render(<ConfigEditor />);
await waitFor( await waitFor(() => {
() => { expect(container.querySelector(selector)).toBeInTheDocument();
expect(container.querySelector(selector)).toBeInTheDocument(); });
},
{ timeout: 5000 },
);
}); });
test('renders a custom placeholder', () => { test('renders a custom placeholder', () => {
@@ -25,32 +25,33 @@ const AsyncComponent = ({ bold }: { bold: boolean }) => (
<span style={{ fontWeight: bold ? 700 : 400 }}>AsyncComponent</span> <span style={{ fontWeight: bold ? 700 : 400 }}>AsyncComponent</span>
); );
const createComponentPromise = () => const ComponentPromise = new Promise(resolve =>
new Promise(resolve => setTimeout(() => resolve(AsyncComponent), 100)); setTimeout(() => resolve(AsyncComponent), 500),
);
test('renders without placeholder', async () => { test('renders without placeholder', async () => {
const Component = AsyncEsmComponent(createComponentPromise()); const Component = AsyncEsmComponent(ComponentPromise);
render(<Component showLoadingForImport={false} />); render(<Component showLoadingForImport={false} />);
expect(screen.queryByRole('status')).not.toBeInTheDocument(); expect(screen.queryByRole('status')).not.toBeInTheDocument();
expect(await screen.findByText('AsyncComponent')).toBeInTheDocument(); expect(await screen.findByText('AsyncComponent')).toBeInTheDocument();
}); });
test('renders with default placeholder', async () => { test('renders with default placeholder', async () => {
const Component = AsyncEsmComponent(createComponentPromise()); const Component = AsyncEsmComponent(ComponentPromise);
render(<Component height={30} showLoadingForImport />); render(<Component height={30} showLoadingForImport />);
expect(screen.getByRole('status')).toBeInTheDocument(); expect(screen.getByRole('status')).toBeInTheDocument();
expect(await screen.findByText('AsyncComponent')).toBeInTheDocument(); expect(await screen.findByText('AsyncComponent')).toBeInTheDocument();
}); });
test('renders with custom placeholder', async () => { test('renders with custom placeholder', async () => {
const Component = AsyncEsmComponent(createComponentPromise(), Placeholder); const Component = AsyncEsmComponent(ComponentPromise, Placeholder);
render(<Component showLoadingForImport />); render(<Component showLoadingForImport />);
expect(screen.getByText('Loading...')).toBeInTheDocument(); expect(screen.getByText('Loading...')).toBeInTheDocument();
expect(await screen.findByText('AsyncComponent')).toBeInTheDocument(); expect(await screen.findByText('AsyncComponent')).toBeInTheDocument();
}); });
test('renders with custom props', async () => { test('renders with custom props', async () => {
const Component = AsyncEsmComponent(createComponentPromise(), Placeholder); const Component = AsyncEsmComponent(ComponentPromise, Placeholder);
render(<Component showLoadingForImport bold />); render(<Component showLoadingForImport bold />);
const asyncComponent = await screen.findByText('AsyncComponent'); const asyncComponent = await screen.findByText('AsyncComponent');
expect(asyncComponent).toBeInTheDocument(); expect(asyncComponent).toBeInTheDocument();
@@ -154,7 +154,7 @@ test('accepts custom style props', () => {
render(<DropdownContainer items={generateItems(2)} style={customStyle} />); render(<DropdownContainer items={generateItems(2)} style={customStyle} />);
const container = screen.getByTestId('container'); const container = screen.getByTestId('container');
expect(container).toHaveStyle('background-color: rgb(255, 0, 0)'); expect(container).toHaveStyle('background-color: red');
expect(container).toHaveStyle('padding: 10px'); expect(container).toHaveStyle('padding: 10px');
}); });
@@ -18,7 +18,7 @@
*/ */
import fetchMock from 'fetch-mock'; import fetchMock from 'fetch-mock';
import { render, screen, waitFor } from '@superset-ui/core/spec'; import { render, screen } from '@superset-ui/core/spec';
import { ImageLoader, type BackgroundPosition } from './ImageLoader'; import { ImageLoader, type BackgroundPosition } from './ImageLoader';
global.URL.createObjectURL = jest.fn(() => '/local_url'); global.URL.createObjectURL = jest.fn(() => '/local_url');
@@ -48,9 +48,7 @@ describe('ImageLoader', () => {
it('is a valid element', async () => { it('is a valid element', async () => {
setup(); setup();
await waitFor(() => { expect(await screen.findByTestId('image-loader')).toBeVisible();
expect(screen.getByTestId('image-loader')).toBeVisible();
});
}); });
it('fetches loads the image in the background', async () => { it('fetches loads the image in the background', async () => {
@@ -127,13 +127,9 @@ const Select = forwardRef(
const shouldShowSearch = allowNewOptions ? true : showSearch; const shouldShowSearch = allowNewOptions ? true : showSearch;
const [selectValue, setSelectValue] = useState(value); const [selectValue, setSelectValue] = useState(value);
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [isLoading, setIsLoading] = useState(loading);
const [isDropdownVisible, setIsDropdownVisible] = useState(false); const [isDropdownVisible, setIsDropdownVisible] = useState(false);
const [isSearching, setIsSearching] = useState(false); const [isSearching, setIsSearching] = useState(false);
const [visibleOptions, setVisibleOptions] = useState<SelectOptionsType>([]); const [visibleOptions, setVisibleOptions] = useState<SelectOptionsType>([]);
const [maxTagCount, setMaxTagCount] = useState(
propsMaxTagCount ?? MAX_TAG_COUNT,
);
const [onChangeCount, setOnChangeCount] = useState(0); const [onChangeCount, setOnChangeCount] = useState(0);
const previousChangeCount = usePrevious(onChangeCount, 0); const previousChangeCount = usePrevious(onChangeCount, 0);
const fireOnChange = useCallback( const fireOnChange = useCallback(
@@ -141,11 +137,11 @@ const Select = forwardRef(
[onChangeCount], [onChangeCount],
); );
useEffect(() => { const maxTagCount = oneLine
if (oneLine) { ? isDropdownVisible
setMaxTagCount(isDropdownVisible ? 0 : 1); ? 0
} : 1
}, [isDropdownVisible, oneLine]); : (propsMaxTagCount ?? MAX_TAG_COUNT);
const mappedMode = isSingleMode ? undefined : 'multiple'; const mappedMode = isSingleMode ? undefined : 'multiple';
@@ -510,6 +506,8 @@ const Select = forwardRef(
], ],
); );
const isLoading = loading ?? false;
const popupRender = ( const popupRender = (
originNode: ReactElement & { ref?: RefObject<HTMLElement> }, originNode: ReactElement & { ref?: RefObject<HTMLElement> },
) => ) =>
@@ -536,12 +534,6 @@ const Select = forwardRef(
setVisibleOptions(initialOptions); setVisibleOptions(initialOptions);
}, [initialOptions]); }, [initialOptions]);
useEffect(() => {
if (loading !== undefined && loading !== isLoading) {
setIsLoading(loading);
}
}, [isLoading, loading]);
useEffect(() => { useEffect(() => {
setSelectValue(value); setSelectValue(value);
}, [value]); }, [value]);
@@ -18,7 +18,7 @@
* under the License. * under the License.
*/ */
import { GenericDataType } from './QueryResponse'; import { GenericDataType } from '@apache-superset/core/api/core';
import { QueryFormColumn } from './QueryFormData'; import { QueryFormColumn } from './QueryFormData';
export interface AdhocColumn { export interface AdhocColumn {
@@ -17,6 +17,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { GenericDataType } from '@apache-superset/core/api/core';
import { DatasourceType } from './Datasource'; import { DatasourceType } from './Datasource';
import { BinaryOperator, SetOperator, UnaryOperator } from './Operator'; import { BinaryOperator, SetOperator, UnaryOperator } from './Operator';
import { AppliedTimeExtras, TimeRange } from './Time'; import { AppliedTimeExtras, TimeRange } from './Time';
@@ -31,7 +32,7 @@ import { Maybe } from '../../types';
import { PostProcessingRule } from './PostProcessing'; import { PostProcessingRule } from './PostProcessing';
import { JsonObject } from '../../connection'; import { JsonObject } from '../../connection';
import { TimeGranularity } from '../../time-format'; import { TimeGranularity } from '../../time-format';
import { GenericDataType, DataRecordValue } from './QueryResponse'; import { DataRecordValue } from './QueryResponse';
export type BaseQueryObjectFilterClause = { export type BaseQueryObjectFilterClause = {
col: QueryFormColumn; col: QueryFormColumn;
@@ -17,19 +17,10 @@
* under the License. * under the License.
*/ */
import { GenericDataType } from '@apache-superset/core/api/core';
import { TimeseriesDataRecord } from '../../chart'; import { TimeseriesDataRecord } from '../../chart';
import { AnnotationData } from './AnnotationLayer'; import { AnnotationData } from './AnnotationLayer';
/**
* 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,
}
/** /**
* Primitive types for data field values. * Primitive types for data field values.
*/ */
@@ -513,7 +513,7 @@ describe('SupersetClientClass', () => {
}); });
}); });
describe.skip('when unauthorized', () => { describe('when unauthorized', () => {
let originalLocation: any; let originalLocation: any;
let authSpy: jest.SpyInstance; let authSpy: jest.SpyInstance;
const mockRequestUrl = 'https://host/get/url'; const mockRequestUrl = 'https://host/get/url';
@@ -354,33 +354,19 @@ describe('callApi()', () => {
}); });
}); });
// TODO: These caching tests require complex jsdom 26 window.location.protocol mocking describe('caching', () => {
// They were skipped during Jest 30/jsdom 26 upgrade due to property redefinition issues const origLocation = window.location;
// Consider implementing with different mocking strategy in future PR
describe.skip('caching', () => {
const originalProtocol = window.location.protocol;
beforeEach(() => { beforeAll(() => {
// jsdom 26+ compatibility: Store and reset protocol per test Object.defineProperty(window, 'location', { value: {} });
Object.defineProperty(window.location, 'protocol', {
value: 'https:',
writable: true,
configurable: true,
});
}); });
afterEach(() => { afterAll(() => {
// Reset protocol after each test Object.defineProperty(window, 'location', { value: origLocation });
if (window.location.protocol !== originalProtocol) {
Object.defineProperty(window.location, 'protocol', {
value: originalProtocol,
writable: true,
configurable: true,
});
}
}); });
beforeEach(async () => { beforeEach(async () => {
window.location.protocol = 'https:';
await caches.delete(constants.CACHE_KEY); await caches.delete(constants.CACHE_KEY);
}); });
@@ -396,13 +382,7 @@ describe('callApi()', () => {
it('will not use cache when running off an insecure connection', async () => { it('will not use cache when running off an insecure connection', async () => {
expect.assertions(2); expect.assertions(2);
window.location.protocol = 'http:';
// Set insecure protocol for this specific test
Object.defineProperty(window.location, 'protocol', {
value: 'http:',
writable: true,
configurable: true,
});
await callApi({ url: mockCacheUrl, method: 'GET' }); await callApi({ url: mockCacheUrl, method: 'GET' });
const calls = fetchMock.calls(mockCacheUrl); const calls = fetchMock.calls(mockCacheUrl);
@@ -16,7 +16,8 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { AdhocMetric, GenericDataType } from '@superset-ui/core'; import { AdhocMetric } from '@superset-ui/core';
import { GenericDataType } from '@apache-superset/core/api/core';
export const NUM_METRIC: AdhocMetric = { export const NUM_METRIC: AdhocMetric = {
expressionType: 'SIMPLE', expressionType: 'SIMPLE',
@@ -1,8 +1,15 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..", "baseUrl": "../..",
"outDir": "lib"
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
}, },
"include": ["src/**/*", "types/**/*"], "include": ["src/**/*", "types/**/*"],
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"], "exclude": ["src/**/*.test.*", "src/**/*.stories.*"],
@@ -20,11 +20,6 @@ module.exports = {
resolve: { resolve: {
...config.resolve, ...config.resolve,
...customConfig.resolve, ...customConfig.resolve,
alias: {
...config.resolve.alias,
...customConfig.resolve.alias,
'react-dom/test-utils': 'react-dom/test-utils.js',
},
}, },
}), }),
@@ -36,36 +36,31 @@
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",
"@mihkeleidast/storybook-addon-source": "^1.0.1", "@mihkeleidast/storybook-addon-source": "^1.0.1",
"@react-icons/all-files": "^4.1.0", "@react-icons/all-files": "^4.1.0",
"@storybook/addon-actions": "8.6.14", "@storybook/addon-actions": "9.0.8",
"@storybook/addon-controls": "8.6.14", "@storybook/addon-controls": "8.1.11",
"@storybook/addon-essentials": "8.6.14", "@storybook/addon-links": "8.1.11",
"@storybook/addon-links": "8.6.14", "@storybook/react": "8.1.11",
"@storybook/react": "8.6.14", "@storybook/types": "8.4.7",
"@storybook/types": "8.6.14",
"@types/react-loadable": "^5.5.11", "@types/react-loadable": "^5.5.11",
"core-js": "3.40.0", "core-js": "3.40.0",
"d3-scale": "4.0.2",
"gh-pages": "^6.3.0", "gh-pages": "^6.3.0",
"handlebars": "^4.7.8",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"memoize-one": "^5.2.1", "memoize-one": "^5.2.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-loadable": "^5.5.0", "react-loadable": "^5.5.0",
"react-resizable": "^3.0.5", "react-resizable": "^3.0.5"
"react-syntax-highlighter": "^15.6.6",
"storybook": "8.6.14"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.28.3", "@babel/core": "^7.28.3",
"@babel/preset-env": "^7.27.2", "@babel/preset-env": "^7.27.2",
"@babel/preset-react": "^7.27.1", "@babel/preset-react": "^7.27.1",
"@babel/preset-typescript": "^7.26.0", "@babel/preset-typescript": "^7.23.3",
"@storybook/react-webpack5": "8.6.14", "@storybook/react-webpack5": "8.2.9",
"babel-loader": "^10.0.0", "babel-loader": "^10.0.0",
"fork-ts-checker-webpack-plugin": "^9.1.0", "fork-ts-checker-webpack-plugin": "^9.1.0",
"ts-loader": "^9.5.2", "ts-loader": "^9.5.2",
"typescript": "5.4.5" "typescript": "^5.7.2"
}, },
"peerDependencies": { "peerDependencies": {
"@encodable/color": "=1.1.1", "@encodable/color": "=1.1.1",
@@ -1,8 +1,15 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..", "baseUrl": "../..",
"outDir": "lib"
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
}, },
"include": ["src/**/*", "types/**/*"], "include": ["src/**/*", "types/**/*"],
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"] "exclude": ["src/**/*.test.*", "src/**/*.stories.*"]
@@ -1,8 +1,15 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..", "baseUrl": "../..",
"outDir": "lib"
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
}, },
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"], "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
"exclude": [ "exclude": [
@@ -1,8 +1,15 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..", "baseUrl": "../..",
"outDir": "lib"
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
}, },
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"], "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
"exclude": [ "exclude": [
@@ -1,8 +1,15 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..", "baseUrl": "../..",
"outDir": "lib"
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
}, },
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"], "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
"exclude": [ "exclude": [
@@ -24,7 +24,7 @@
], ],
"dependencies": { "dependencies": {
"d3-array": "^2.0.3", "d3-array": "^2.0.3",
"d3-scale": "^4.0.2", "d3-scale": "^3.0.1",
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
}, },
"peerDependencies": { "peerDependencies": {
@@ -1,8 +1,15 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..", "baseUrl": "../..",
"outDir": "lib"
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
}, },
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"], "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
"exclude": [ "exclude": [
@@ -1,8 +1,15 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..", "baseUrl": "../..",
"outDir": "lib"
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
}, },
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"], "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
"exclude": [ "exclude": [
@@ -1,8 +1,15 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..", "baseUrl": "../..",
"outDir": "lib"
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
}, },
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"], "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
"exclude": [ "exclude": [
@@ -1,8 +1,15 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..", "baseUrl": "../..",
"outDir": "lib"
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
}, },
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"], "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
"exclude": [ "exclude": [
@@ -1,8 +1,15 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..", "baseUrl": "../..",
"outDir": "lib"
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
}, },
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"], "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
"exclude": [ "exclude": [
@@ -1,8 +1,15 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..", "baseUrl": "../..",
"outDir": "lib"
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
}, },
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"], "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
"exclude": [ "exclude": [
@@ -31,13 +31,13 @@
"dependencies": { "dependencies": {
"d3": "^3.5.17", "d3": "^3.5.17",
"d3-array": "^2.4.0", "d3-array": "^2.4.0",
"d3-color": "^3.1.0",
"datamaps": "^0.5.9", "datamaps": "^0.5.9",
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@superset-ui/chart-controls": "*", "@superset-ui/chart-controls": "*",
"@superset-ui/core": "*", "@superset-ui/core": "*",
"react": "^17.0.2", "react": "^17.0.2"
"tinycolor2": "*"
} }
} }
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import tinycolor from 'tinycolor2'; import { rgb } from 'd3-color';
import { getValueFormatter } from '@superset-ui/core'; import { getValueFormatter } from '@superset-ui/core';
export default function transformProps(chartProps) { export default function transformProps(chartProps) {
@@ -66,7 +66,7 @@ export default function transformProps(chartProps) {
maxBubbleSize: parseInt(maxBubbleSize, 10), maxBubbleSize: parseInt(maxBubbleSize, 10),
showBubbles, showBubbles,
linearColorScheme, linearColorScheme,
color: tinycolor({ r, g, b }).toHexString(), color: rgb(r, g, b).hex(),
colorBy, colorBy,
colorScheme, colorScheme,
sliceId, sliceId,
@@ -1,8 +1,15 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
"baseUrl": "../..", "baseUrl": "../..",
"outDir": "lib"
// Directory Overrides: Parent config paths are relative to frontend root,
// but packages need paths relative to their own directory
"outDir": "lib",
"rootDir": "src",
"declarationDir": "lib"
}, },
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"], "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
"exclude": [ "exclude": [
@@ -26,26 +26,23 @@
"dependencies": { "dependencies": {
"@deck.gl/aggregation-layers": "^9.1.14", "@deck.gl/aggregation-layers": "^9.1.14",
"@deck.gl/core": "^9.1.14", "@deck.gl/core": "^9.1.14",
"@deck.gl/extensions": "^9.1.14",
"@deck.gl/geo-layers": "^9.1.13", "@deck.gl/geo-layers": "^9.1.13",
"@deck.gl/layers": "^9.1.13", "@deck.gl/layers": "^9.1.13",
"@deck.gl/mesh-layers": "^9.1.14",
"@deck.gl/react": "^9.1.14", "@deck.gl/react": "^9.1.14",
"@deck.gl/widgets": "^9.1.14",
"@luma.gl/constants": "^9.1.9", "@luma.gl/constants": "^9.1.9",
"@luma.gl/core": "^9.1.9", "@luma.gl/core": "^9.1.9",
"@luma.gl/engine": "^9.1.9", "@luma.gl/engine": "^9.1.9",
"@luma.gl/shadertools": "^9.1.9", "@luma.gl/shadertools": "^9.1.9",
"@luma.gl/webgl": "^9.1.9", "@luma.gl/webgl": "^9.1.9",
"@mapbox/geojson-extent": "^1.0.1",
"@mapbox/tiny-sdf": "^2.0.6", "@mapbox/tiny-sdf": "^2.0.6",
"@mapbox/geojson-extent": "^1.0.1",
"@math.gl/web-mercator": "^4.1.0", "@math.gl/web-mercator": "^4.1.0",
"@types/d3-array": "^2.0.0", "@types/d3-array": "^2.0.0",
"@types/geojson": "^7946.0.16", "@types/geojson": "^7946.0.16",
"bootstrap-slider": "^11.0.2", "bootstrap-slider": "^11.0.2",
"d3-array": "^1.2.4", "d3-array": "^1.2.4",
"d3-color": "^3.1.0", "d3-color": "^1.4.1",
"d3-scale": "^4.0.2", "d3-scale": "^3.0.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@@ -68,8 +65,7 @@
"mapbox-gl": "*", "mapbox-gl": "*",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-map-gl": "^6.1.19", "react-map-gl": "^6.1.19"
"tinycolor2": "*"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
@@ -169,12 +169,12 @@ const CategoricalDeckGLContainer = (props: CategoricalDeckGLContainerProps) => {
})); }));
} }
case COLOR_SCHEME_TYPES.color_breakpoints: { case COLOR_SCHEME_TYPES.color_breakpoints: {
const defaultBreakpointColor = fd.deafult_breakpoint_color const defaultBreakpointColor = fd.default_breakpoint_color
? [ ? [
fd.deafult_breakpoint_color.r, fd.default_breakpoint_color.r,
fd.deafult_breakpoint_color.g, fd.default_breakpoint_color.g,
fd.deafult_breakpoint_color.b, fd.default_breakpoint_color.b,
fd.deafult_breakpoint_color.a * 255, fd.default_breakpoint_color.a * 255,
] ]
: [ : [
DEFAULT_DECKGL_COLOR.r, DEFAULT_DECKGL_COLOR.r,
@@ -0,0 +1,96 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
buildQueryContext,
ensureIsArray,
SqlaFormData,
} from '@superset-ui/core';
import {
getSpatialColumns,
addSpatialNullFilters,
SpatialFormData,
} from '../spatialUtils';
import { addTooltipColumnsToQuery } from '../buildQueryUtils';
export interface DeckArcFormData extends SqlaFormData {
start_spatial: SpatialFormData['spatial'];
end_spatial: SpatialFormData['spatial'];
dimension?: string;
js_columns?: string[];
tooltip_contents?: unknown[];
tooltip_template?: string;
}
export default function buildQuery(formData: DeckArcFormData) {
const {
start_spatial,
end_spatial,
dimension,
js_columns,
tooltip_contents,
} = formData;
if (!start_spatial || !end_spatial) {
throw new Error(
'Start and end spatial configurations are required for Arc charts',
);
}
return buildQueryContext(formData, baseQueryObject => {
const startSpatialColumns = getSpatialColumns(start_spatial);
const endSpatialColumns = getSpatialColumns(end_spatial);
let columns = [
...(baseQueryObject.columns || []),
...startSpatialColumns,
...endSpatialColumns,
];
if (dimension) {
columns = [...columns, dimension];
}
const jsColumns = ensureIsArray(js_columns || []);
jsColumns.forEach(col => {
if (!columns.includes(col)) {
columns.push(col);
}
});
columns = addTooltipColumnsToQuery(columns, tooltip_contents);
let filters = addSpatialNullFilters(
start_spatial,
ensureIsArray(baseQueryObject.filters || []),
);
filters = addSpatialNullFilters(end_spatial, filters);
const isTimeseries = !!formData.time_grain_sqla;
return [
{
...baseQueryObject,
columns,
filters,
is_timeseries: isTimeseries,
row_limit: baseQueryObject.row_limit,
},
];
});
}
@@ -21,7 +21,8 @@ import thumbnail from './images/thumbnail.png';
import thumbnailDark from './images/thumbnail-dark.png'; import thumbnailDark from './images/thumbnail-dark.png';
import example from './images/example.png'; import example from './images/example.png';
import exampleDark from './images/example-dark.png'; import exampleDark from './images/example-dark.png';
import transformProps from '../../transformProps'; import transformProps from './transformProps';
import buildQuery from './buildQuery';
import controlPanel from './controlPanel'; import controlPanel from './controlPanel';
const metadata = new ChartMetadata({ const metadata = new ChartMetadata({
@@ -39,13 +40,13 @@ const metadata = new ChartMetadata({
thumbnail, thumbnail,
thumbnailDark, thumbnailDark,
exampleGallery: [{ url: example, urlDark: exampleDark }], exampleGallery: [{ url: example, urlDark: exampleDark }],
useLegacyApi: true,
tags: [t('deckGL'), t('Geo'), t('3D'), t('Relational'), t('Web')], tags: [t('deckGL'), t('Geo'), t('3D'), t('Relational'), t('Web')],
}); });
export default class ArcChartPlugin extends ChartPlugin { export default class ArcChartPlugin extends ChartPlugin {
constructor() { constructor() {
super({ super({
buildQuery,
loadChart: () => import('./Arc'), loadChart: () => import('./Arc'),
controlPanel, controlPanel,
metadata, metadata,
@@ -0,0 +1,108 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ChartProps } from '@superset-ui/core';
import {
processSpatialData,
addJsColumnsToExtraProps,
DataRecord,
} from '../spatialUtils';
import {
createBaseTransformResult,
getRecordsFromQuery,
addPropertiesToFeature,
} from '../transformUtils';
import { DeckArcFormData } from './buildQuery';
interface ArcPoint {
sourcePosition: [number, number];
targetPosition: [number, number];
cat_color?: string;
__timestamp?: number;
extraProps?: Record<string, unknown>;
[key: string]: unknown;
}
function processArcData(
records: DataRecord[],
startSpatial: DeckArcFormData['start_spatial'],
endSpatial: DeckArcFormData['end_spatial'],
dimension?: string,
jsColumns?: string[],
): ArcPoint[] {
if (!startSpatial || !endSpatial || !records.length) {
return [];
}
const startFeatures = processSpatialData(records, startSpatial);
const endFeatures = processSpatialData(records, endSpatial);
const excludeKeys = new Set(
['__timestamp', dimension, ...(jsColumns || [])].filter(
(key): key is string => key != null,
),
);
return records
.map((record, index) => {
const startFeature = startFeatures[index];
const endFeature = endFeatures[index];
if (!startFeature || !endFeature) {
return null;
}
let arcPoint: ArcPoint = {
sourcePosition: startFeature.position,
targetPosition: endFeature.position,
extraProps: {},
};
arcPoint = addJsColumnsToExtraProps(arcPoint, record, jsColumns);
if (dimension && record[dimension] != null) {
arcPoint.cat_color = String(record[dimension]);
}
// eslint-disable-next-line no-underscore-dangle
if (record.__timestamp != null) {
// eslint-disable-next-line no-underscore-dangle
arcPoint.__timestamp = Number(record.__timestamp);
}
arcPoint = addPropertiesToFeature(arcPoint, record, excludeKeys);
return arcPoint;
})
.filter((point): point is ArcPoint => point !== null);
}
export default function transformProps(chartProps: ChartProps) {
const { rawFormData: formData } = chartProps;
const { start_spatial, end_spatial, dimension, js_columns } =
formData as DeckArcFormData;
const records = getRecordsFromQuery(chartProps.queriesData);
const features = processArcData(
records,
start_spatial,
end_spatial,
dimension,
js_columns,
);
return createBaseTransformResult(chartProps, features);
}
@@ -0,0 +1,34 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SpatialFormData, buildSpatialQuery } from '../spatialUtils';
export interface DeckContourFormData extends SpatialFormData {
cellSize?: string;
aggregation?: string;
contours?: Array<{
color: { r: number; g: number; b: number };
lowerThreshold: number;
upperThreshold?: number;
strokeWidth?: number;
}>;
}
export default function buildQuery(formData: DeckContourFormData) {
return buildSpatialQuery(formData);
}
@@ -17,12 +17,13 @@
* under the License. * under the License.
*/ */
import { t, ChartMetadata, ChartPlugin, Behavior } from '@superset-ui/core'; import { t, ChartMetadata, ChartPlugin, Behavior } from '@superset-ui/core';
import transformProps from '../../transformProps';
import controlPanel from './controlPanel';
import thumbnail from './images/thumbnail.png'; import thumbnail from './images/thumbnail.png';
import thumbnailDark from './images/thumbnail-dark.png'; import thumbnailDark from './images/thumbnail-dark.png';
import example from './images/example.png'; import example from './images/example.png';
import exampleDark from './images/example-dark.png'; import exampleDark from './images/example-dark.png';
import buildQuery from './buildQuery';
import transformProps from './transformProps';
import controlPanel from './controlPanel';
const metadata = new ChartMetadata({ const metadata = new ChartMetadata({
category: t('Map'), category: t('Map'),
@@ -34,7 +35,6 @@ const metadata = new ChartMetadata({
name: t('deck.gl Contour'), name: t('deck.gl Contour'),
thumbnail, thumbnail,
thumbnailDark, thumbnailDark,
useLegacyApi: true,
tags: [t('deckGL'), t('Spatial'), t('Comparison')], tags: [t('deckGL'), t('Spatial'), t('Comparison')],
behaviors: [Behavior.InteractiveChart], behaviors: [Behavior.InteractiveChart],
}); });
@@ -42,6 +42,7 @@ const metadata = new ChartMetadata({
export default class ContourChartPlugin extends ChartPlugin { export default class ContourChartPlugin extends ChartPlugin {
constructor() { constructor() {
super({ super({
buildQuery,
loadChart: () => import('./Contour'), loadChart: () => import('./Contour'),
controlPanel, controlPanel,
metadata, metadata,
@@ -16,6 +16,6 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { transformSpatialProps } from '../spatialUtils';
export { default as DrillDetailMenuItems } from './DrillDetailMenuItems'; export default transformSpatialProps;
export { useDrillDetailMenuItems } from './useDrillDetailMenuItems';
@@ -76,7 +76,7 @@ export const getLayer: GetLayerType<GridLayer> = function ({
const colorSchemeType = fd.color_scheme_type; const colorSchemeType = fd.color_scheme_type;
const colorRange = getColorRange({ const colorRange = getColorRange({
defaultBreakpointsColor: fd.deafult_breakpoint_color, defaultBreakpointsColor: fd.default_breakpoint_color,
colorSchemeType, colorSchemeType,
colorScale, colorScale,
colorBreakpoints, colorBreakpoints,
@@ -16,36 +16,12 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { useToasts } from 'src/components/MessageToasts/withToasts'; import { SpatialFormData, buildSpatialQuery } from '../spatialUtils';
import { useComponentDidMount } from '@superset-ui/core';
import type { FlashMessage } from './types';
interface Props { export interface DeckGridFormData extends SpatialFormData {
children: JSX.Element; extruded?: boolean;
messages: FlashMessage[];
} }
const flashObj = { export default function buildQuery(formData: DeckGridFormData) {
info: 'addInfoToast', return buildSpatialQuery(formData);
alert: 'addDangerToast',
danger: 'addDangerToast',
warning: 'addWarningToast',
success: 'addSuccessToast',
};
export function FlashProvider({ children, messages }: Props) {
const toasts = useToasts();
useComponentDidMount(() => {
messages.forEach(message => {
const [type, text] = message;
const flash = flashObj[type];
const toast = toasts[flash as keyof typeof toasts];
if (toast) {
toast(text);
}
});
});
return children;
} }
export type { FlashMessage };
@@ -21,7 +21,8 @@ import thumbnail from './images/thumbnail.png';
import thumbnailDark from './images/thumbnail-dark.png'; import thumbnailDark from './images/thumbnail-dark.png';
import example from './images/example.png'; import example from './images/example.png';
import exampleDark from './images/example-dark.png'; import exampleDark from './images/example-dark.png';
import transformProps from '../../transformProps'; import buildQuery from './buildQuery';
import transformProps from './transformProps';
import controlPanel from './controlPanel'; import controlPanel from './controlPanel';
const metadata = new ChartMetadata({ const metadata = new ChartMetadata({
@@ -34,7 +35,6 @@ const metadata = new ChartMetadata({
thumbnail, thumbnail,
thumbnailDark, thumbnailDark,
exampleGallery: [{ url: example, urlDark: exampleDark }], exampleGallery: [{ url: example, urlDark: exampleDark }],
useLegacyApi: true,
tags: [t('deckGL'), t('3D'), t('Comparison')], tags: [t('deckGL'), t('3D'), t('Comparison')],
behaviors: [Behavior.InteractiveChart], behaviors: [Behavior.InteractiveChart],
}); });
@@ -42,6 +42,7 @@ const metadata = new ChartMetadata({
export default class GridChartPlugin extends ChartPlugin { export default class GridChartPlugin extends ChartPlugin {
constructor() { constructor() {
super({ super({
buildQuery,
loadChart: () => import('./Grid'), loadChart: () => import('./Grid'),
controlPanel, controlPanel,
metadata, metadata,
@@ -0,0 +1,24 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ChartProps } from '@superset-ui/core';
import { transformSpatialProps } from '../spatialUtils';
export default function transformProps(chartProps: ChartProps) {
return transformSpatialProps(chartProps);
}
@@ -126,7 +126,7 @@ export const getLayer: GetLayerType<HeatmapLayer> = ({
const colorSchemeType = fd.color_scheme_type; const colorSchemeType = fd.color_scheme_type;
const colorRange = getColorRange({ const colorRange = getColorRange({
defaultBreakpointsColor: fd.deafult_breakpoint_color, defaultBreakpointsColor: fd.default_breakpoint_color,
colorBreakpoints: fd.color_breakpoints, colorBreakpoints: fd.color_breakpoints,
fixedColor: fd.color_picker, fixedColor: fd.color_picker,
colorSchemeType, colorSchemeType,
@@ -16,9 +16,8 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { SpatialFormData, buildSpatialQuery } from '../spatialUtils';
// Mock dom-to-pdf module for Jest tests export default function buildQuery(formData: SpatialFormData) {
// The real module requires TextEncoder which isn't available in Node.js test environment return buildSpatialQuery(formData);
const domToPdf = jest.fn(() => Promise.resolve()); }
export default domToPdf;

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