Compare commits
110 Commits
fix-docker
...
fix-timezo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b8f2f3f94 | ||
|
|
a3d876c64f | ||
|
|
c0763fe58f | ||
|
|
2f2ce330b2 | ||
|
|
f4708a5648 | ||
|
|
b9ab03994a | ||
|
|
df253f6aa4 | ||
|
|
5cea4fb7fe | ||
|
|
76a27d5360 | ||
|
|
174e3c26d3 | ||
|
|
9ea5ded988 | ||
|
|
9086ae8e6c | ||
|
|
fc5506e466 | ||
|
|
e9ae212c1c | ||
|
|
46bca32677 | ||
|
|
a04571fa20 | ||
|
|
fc26dbfebf | ||
|
|
0415118544 | ||
|
|
935bbe6061 | ||
|
|
ec6eaf4898 | ||
|
|
87d15d32c4 | ||
|
|
7d9a8a0c5a | ||
|
|
ddba88ffad | ||
|
|
1e50422a66 | ||
|
|
246dbd7f5c | ||
|
|
9b861b2848 | ||
|
|
1c35c3f6d0 | ||
|
|
b71654877f | ||
|
|
cd447ca1fd | ||
|
|
01ac966b83 | ||
|
|
97e5f0631d | ||
|
|
b7acb7984f | ||
|
|
d3919cf24f | ||
|
|
27889651b3 | ||
|
|
361fe6fe89 | ||
|
|
8506d70242 | ||
|
|
00a53eec2d | ||
|
|
5040db859c | ||
|
|
ef4f7afa90 | ||
|
|
47db185e3b | ||
|
|
2e463078a2 | ||
|
|
4f42928b34 | ||
|
|
75fa474fce | ||
|
|
fd8c21591a | ||
|
|
4147d877fc | ||
|
|
a9dca529c1 | ||
|
|
20f1918dd6 | ||
|
|
c09a4f6f47 | ||
|
|
4e4fa53c8d | ||
|
|
07ff82f189 | ||
|
|
b7b9bfd3fe | ||
|
|
b968d1095c | ||
|
|
e10237fcc1 | ||
|
|
92438322c0 | ||
|
|
f96e90b979 | ||
|
|
b464979db1 | ||
|
|
45f883c9cd | ||
|
|
8fd3401077 | ||
|
|
89a98ab9a4 | ||
|
|
2dfc770b0f | ||
|
|
6b7b23ed78 | ||
|
|
5ac5480f35 | ||
|
|
76889c1a69 | ||
|
|
569606635b | ||
|
|
66264856a7 | ||
|
|
3eb860a663 | ||
|
|
a44980da65 | ||
|
|
7112bce961 | ||
|
|
568486a304 | ||
|
|
fea135b46c | ||
|
|
601fcb3382 | ||
|
|
0d7cc88b2b | ||
|
|
32ee160c75 | ||
|
|
5914e83436 | ||
|
|
0b5e4dd5de | ||
|
|
3a565a6c16 | ||
|
|
f60c82e4a6 | ||
|
|
91131d5996 | ||
|
|
4b0d497513 | ||
|
|
86f690d17f | ||
|
|
e9b494163b | ||
|
|
be404f9b84 | ||
|
|
11257c0536 | ||
|
|
f2b6c395cd | ||
|
|
2d35ed2391 | ||
|
|
bd65469091 | ||
|
|
a6a66ca483 | ||
|
|
4a7cdccdad | ||
|
|
61bd8f0cf2 | ||
|
|
ae10e105c2 | ||
|
|
901dca58f7 | ||
|
|
d95a3d8426 | ||
|
|
70b95ca1b9 | ||
|
|
004f02746f | ||
|
|
5d20dc57d7 | ||
|
|
05c2354997 | ||
|
|
6043e7e7e3 | ||
|
|
1ee14c5993 | ||
|
|
9764a84402 | ||
|
|
570cc3e5f8 | ||
|
|
66519c3a85 | ||
|
|
1f43138888 | ||
|
|
652d029a2d | ||
|
|
e67b1f5326 | ||
|
|
fa79a467e4 | ||
|
|
2cce0308d4 | ||
|
|
c7fd1a2f65 | ||
|
|
ab4f646ef6 | ||
|
|
d6029f5c8a | ||
|
|
c16e8f747c |
2
.github/CODEOWNERS
vendored
@@ -20,7 +20,7 @@
|
||||
|
||||
# Notify PMC members of changes to GitHub Actions
|
||||
|
||||
/.github/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar @sadpandajoe
|
||||
/.github/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar @sadpandajoe @hainenber
|
||||
|
||||
# Notify PMC members of changes to required GitHub Actions
|
||||
|
||||
|
||||
24
.github/dependabot.yml
vendored
@@ -9,9 +9,12 @@ updates:
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
ignore:
|
||||
# not until React >= 18.0.0
|
||||
# TODO: remove below entries until React >= 18.0.0
|
||||
- dependency-name: "storybook"
|
||||
update-types: ["version-update:semver-major", "version-update:semver-minor"]
|
||||
- dependency-name: "@storybook*"
|
||||
update-types: ["version-update:semver-major", "version-update:semver-minor"]
|
||||
- dependency-name: "eslint-plugin-storybook"
|
||||
# remark-gfm v4+ requires react-markdown v9+, which needs React 18
|
||||
- dependency-name: "remark-gfm"
|
||||
- dependency-name: "react-markdown"
|
||||
@@ -23,6 +26,14 @@ updates:
|
||||
# See https://github.com/apache/superset/pull/37384#issuecomment-3793991389
|
||||
# TODO: remove the plugin once Lodash usage has been migrated to a more readily tree-shakeable alternative
|
||||
- dependency-name: "@swc/plugin-transform-imports"
|
||||
groups:
|
||||
storybook:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "@storybook*"
|
||||
- "storybook"
|
||||
update-types:
|
||||
- "patch"
|
||||
directory: "/superset-frontend/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
@@ -355,6 +366,17 @@ updates:
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/packages/superset-ui-demo/"
|
||||
ignore:
|
||||
# TODO: remove below entries until React >= 18.0.0
|
||||
- dependency-name: "@storybook*"
|
||||
update-types: ["version-update:semver-major", "version-update:semver-minor"]
|
||||
groups:
|
||||
storybook:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "@storybook*"
|
||||
update-types:
|
||||
- "patch"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
|
||||
2
.github/workflows/ephemeral-env-pr-close.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
|
||||
4
.github/workflows/ephemeral-env.yml
vendored
@@ -189,7 +189,7 @@ jobs:
|
||||
--extra-flags "--build-arg INCLUDE_CHROMIUM=false"
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
@@ -225,7 +225,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
|
||||
70
.github/workflows/prefer-typescript.yml
vendored
@@ -1,70 +0,0 @@
|
||||
name: Prefer TypeScript
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
- "[0-9].[0-9]*"
|
||||
paths:
|
||||
- "superset-frontend/src/**"
|
||||
pull_request:
|
||||
types: [synchronize, opened, reopened, ready_for_review]
|
||||
paths:
|
||||
- "superset-frontend/src/**"
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
prefer_typescript:
|
||||
if: github.ref == 'ref/heads/master' && github.event_name == 'pull_request'
|
||||
name: Prefer TypeScript
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Get changed files
|
||||
id: changed
|
||||
uses: ./.github/actions/file-changes-action
|
||||
with:
|
||||
githubToken: ${{ github.token }}
|
||||
|
||||
- name: Determine if a .js or .jsx file was added
|
||||
id: check
|
||||
run: |
|
||||
js_files_added() {
|
||||
jq -r '
|
||||
map(
|
||||
select(
|
||||
endswith(".js") or endswith(".jsx")
|
||||
)
|
||||
) | join("\n")
|
||||
' ${HOME}/files_added.json
|
||||
}
|
||||
echo "js_files_added=$(js_files_added)" >> $GITHUB_OUTPUT
|
||||
|
||||
- if: steps.check.outputs.js_files_added
|
||||
name: Add Comment to PR
|
||||
uses: ./.github/actions/comment-on-pr
|
||||
continue-on-error: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
msg: |
|
||||
### WARNING: Prefer TypeScript
|
||||
|
||||
Looks like your PR contains new `.js` or `.jsx` files:
|
||||
|
||||
```
|
||||
${{steps.check.outputs.js_files_added}}
|
||||
```
|
||||
|
||||
As decided in [SIP-36](https://github.com/apache/superset/issues/9101), all new frontend code should be written in TypeScript. Please convert above files to TypeScript then re-request review.
|
||||
4
.github/workflows/superset-docs-deploy.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
||||
yarn install --check-cache
|
||||
- name: Download database diagnostics (if triggered by integration tests)
|
||||
if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success'
|
||||
uses: dawidd6/action-download-artifact@v12
|
||||
uses: dawidd6/action-download-artifact@v14
|
||||
continue-on-error: true
|
||||
with:
|
||||
workflow: superset-python-integrationtest.yml
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
path: docs/src/data/
|
||||
- name: Try to download latest diagnostics (for push/dispatch triggers)
|
||||
if: github.event_name != 'workflow_run'
|
||||
uses: dawidd6/action-download-artifact@v12
|
||||
uses: dawidd6/action-download-artifact@v14
|
||||
continue-on-error: true
|
||||
with:
|
||||
workflow: superset-python-integrationtest.yml
|
||||
|
||||
4
.github/workflows/superset-docs-verify.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
# Do not bump this linkinator-action version without opening
|
||||
# an ASF Infra ticket to allow the new version first!
|
||||
- uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3
|
||||
- uses: JustinBeckwith/linkinator-action@f62ba0c110a76effb2ee6022cc6ce4ab161085e3 # v2.4
|
||||
continue-on-error: true # This will make the job advisory (non-blocking, no red X)
|
||||
with:
|
||||
paths: "**/*.md, **/*.mdx"
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
run: |
|
||||
yarn install --check-cache
|
||||
- name: Download database diagnostics from integration tests
|
||||
uses: dawidd6/action-download-artifact@v12
|
||||
uses: dawidd6/action-download-artifact@v14
|
||||
with:
|
||||
workflow: superset-python-integrationtest.yml
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
@@ -27,6 +27,7 @@ repos:
|
||||
args: [--check-untyped-defs]
|
||||
exclude: ^superset-extensions-cli/
|
||||
additional_dependencies: [
|
||||
types-cachetools,
|
||||
types-simplejson,
|
||||
types-python-dateutil,
|
||||
types-requests,
|
||||
|
||||
@@ -430,6 +430,11 @@ categories:
|
||||
url: https://brandct.cn/
|
||||
contributors: ["@wenbinye"]
|
||||
|
||||
- name: XNET
|
||||
url: https://xnetmobile.com/
|
||||
logo: xnet.png
|
||||
contributors: ["@deuspt"]
|
||||
|
||||
- name: Zeta
|
||||
url: https://www.zeta.tech/
|
||||
contributors: ["@shaikidris"]
|
||||
|
||||
15
UPDATING.md
@@ -24,6 +24,21 @@ assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
|
||||
### WebSocket config for GAQ with Docker
|
||||
|
||||
[35896](https://github.com/apache/superset/pull/35896) and [37624](https://github.com/apache/superset/pull/37624) updated documentation on how to run and configure Superset with Docker. Specifically for the WebSocket configuration, a new `docker/superset-websocket/config.example.json` was added to the repo, so that users could copy it to create a `docker/superset-websocket/config.json` file. The existing `docker/superset-websocket/config.json` was removed and git-ignored, so if you're using GAQ / WebSocket make sure to:
|
||||
- Stash/backup your existing `config.json` file, to re-apply it after (will get git-ignored going forward)
|
||||
- Update the `volumes` configuration for the `superset-websocket` service in your `docker-compose.override.yml` file, to include the `docker/superset-websocket/config.json` file. For example:
|
||||
``` yaml
|
||||
services:
|
||||
superset-websocket:
|
||||
volumes:
|
||||
- ./superset-websocket:/home/superset-websocket
|
||||
- /home/superset-websocket/node_modules
|
||||
- /home/superset-websocket/dist
|
||||
- ./docker/superset-websocket/config.json:/home/superset-websocket/config.json:ro
|
||||
```
|
||||
|
||||
### Example Data Loading Improvements
|
||||
|
||||
#### New Directory Structure
|
||||
|
||||
@@ -159,8 +159,8 @@ services:
|
||||
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
|
||||
# configuring the dev-server to use the host.docker.internal to connect to the backend
|
||||
superset: "http://superset-light:8088"
|
||||
# Webpack dev server configuration
|
||||
WEBPACK_DEVSERVER_HOST: "${WEBPACK_DEVSERVER_HOST:-127.0.0.1}"
|
||||
# Webpack dev server must bind to 0.0.0.0 to be accessible from outside the container
|
||||
WEBPACK_DEVSERVER_HOST: "${WEBPACK_DEVSERVER_HOST:-0.0.0.0}"
|
||||
WEBPACK_DEVSERVER_PORT: "${WEBPACK_DEVSERVER_PORT:-9000}"
|
||||
ports:
|
||||
- "${NODE_PORT:-9001}:9000" # Parameterized port, accessible on all interfaces
|
||||
|
||||
@@ -175,7 +175,7 @@ services:
|
||||
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
|
||||
# configuring the dev-server to use the host.docker.internal to connect to the backend
|
||||
superset: "http://superset:8088"
|
||||
# Bind to all interfaces so Docker port mapping works
|
||||
# Webpack dev server must bind to 0.0.0.0 to be accessible from outside the container
|
||||
WEBPACK_DEVSERVER_HOST: "0.0.0.0"
|
||||
ports:
|
||||
- "127.0.0.1:${NODE_PORT:-9000}:9000" # exposing the dynamic webpack dev server
|
||||
|
||||
@@ -28,11 +28,11 @@ if [ "$BUILD_SUPERSET_FRONTEND_IN_DOCKER" = "true" ]; then
|
||||
cd /app/superset-frontend
|
||||
|
||||
if [ "$NPM_RUN_PRUNE" = "true" ]; then
|
||||
echo "Running `npm run prune`"
|
||||
echo "Running \"npm run prune\""
|
||||
npm run prune
|
||||
fi
|
||||
|
||||
echo "Running `npm install`"
|
||||
echo "Running \"npm install\""
|
||||
npm install
|
||||
|
||||
echo "Start webpack dev server"
|
||||
|
||||
@@ -105,7 +105,7 @@ class CeleryConfig:
|
||||
|
||||
CELERY_CONFIG = CeleryConfig
|
||||
|
||||
FEATURE_FLAGS = {"ALERT_REPORTS": True}
|
||||
FEATURE_FLAGS = {"ALERT_REPORTS": True, "DATASET_FOLDERS": True}
|
||||
ALERT_REPORTS_NOTIFICATION_DRY_RUN = True
|
||||
WEBDRIVER_BASEURL = f"http://superset_app{os.environ.get('SUPERSET_APP_ROOT', '/')}/" # When using docker compose baseurl should be http://superset_nginx{ENV{BASEPATH}}/ # noqa: E501
|
||||
# The base URL for the email report hyperlinks.
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"port": 8080,
|
||||
"logLevel": "info",
|
||||
"logToFile": false,
|
||||
"logFilename": "app.log",
|
||||
"statsd": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 8125,
|
||||
"globalTags": []
|
||||
},
|
||||
"redis": {
|
||||
"port": 6379,
|
||||
"host": "127.0.0.1",
|
||||
"password": "",
|
||||
"db": 0,
|
||||
"ssl": false
|
||||
},
|
||||
"redisStreamPrefix": "async-events-",
|
||||
"jwtAlgorithms": ["HS256"],
|
||||
"jwtSecret": "CHANGE-ME-IN-PRODUCTION-GOTTA-BE-LONG-AND-SECRET",
|
||||
"jwtCookieName": "async-token"
|
||||
}
|
||||
@@ -788,7 +788,7 @@ pytest ./link_to_test.py
|
||||
|
||||
### Frontend Testing
|
||||
|
||||
We use [Jest](https://jestjs.io/) and [Enzyme](https://airbnb.io/enzyme/) to test TypeScript/JavaScript. Tests can be run with:
|
||||
We use [Jest](https://jestjs.io/) and [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) to test TypeScript. Tests can be run with:
|
||||
|
||||
```bash
|
||||
cd superset-frontend
|
||||
|
||||
@@ -100,7 +100,7 @@ npm link superset-plugin-chart-hello-world
|
||||
```
|
||||
|
||||
7. **Import and register in Superset**:
|
||||
Edit `superset-frontend/src/visualizations/presets/MainPreset.js` to include your plugin.
|
||||
Edit `superset-frontend/src/visualizations/presets/MainPreset.ts` to include your plugin.
|
||||
|
||||
## Testing
|
||||
|
||||
|
||||
@@ -134,9 +134,9 @@ export const onDidChangeActivePanel: Event<Panel>;
|
||||
|
||||
export const onDidChangeTabTitle: Event<string>;
|
||||
|
||||
export const onDidQueryRun: Event<Editor>;
|
||||
export const onDidQueryRun: Event<QueryContext>;
|
||||
|
||||
export const onDidQueryStop: Event<Editor>;
|
||||
export const onDidQueryStop: Event<QueryContext>;
|
||||
```
|
||||
|
||||
The following code demonstrates more examples of the existing frontend APIs:
|
||||
@@ -150,16 +150,16 @@ export function activate(context) {
|
||||
const panelDisposable = core.registerView('my_extension.panel', <MyPanel><Button/></MyPanel>);
|
||||
|
||||
// Register a custom command
|
||||
const commandDisposable = commands.registerCommand('my_extension.copy_query', {
|
||||
title: 'Copy Query',
|
||||
execute: () => {
|
||||
const commandDisposable = commands.registerCommand(
|
||||
'my_extension.copy_query',
|
||||
() => {
|
||||
// Command logic here
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
// Listen for query run events in SQL Lab
|
||||
const eventDisposable = sqlLab.onDidQueryRun(editor => {
|
||||
// Handle query execution event
|
||||
const eventDisposable = sqlLab.onDidQueryRun(queryContext => {
|
||||
console.log('Query started on database:', queryContext.tab.databaseId);
|
||||
});
|
||||
|
||||
// Access a CSRF token for secure API requests
|
||||
|
||||
@@ -24,7 +24,7 @@ under the License.
|
||||
|
||||
# SQL Lab Extension Points
|
||||
|
||||
SQL Lab provides 5 extension points where extensions can contribute custom UI components. Each area serves a specific purpose and can be customized to add new functionality.
|
||||
SQL Lab provides 4 extension points where extensions can contribute custom UI components. Each area serves a specific purpose and supports different types of customizations. These areas will evolve over time as new features are added to SQL Lab.
|
||||
|
||||
## Layout Overview
|
||||
|
||||
@@ -41,42 +41,44 @@ SQL Lab provides 5 extension points where extensions can contribute custom UI co
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
├──────────┴─────────────────────────────────────────┴─────────────┤
|
||||
│ Status Bar │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
└──────────┴─────────────────────────────────────────┴─────────────┘
|
||||
```
|
||||
|
||||
| Extension Point | ID | Description |
|
||||
| ----------------- | --------------------- | ---------------------------------------------------------- |
|
||||
| **Left Sidebar** | `sqllab.leftSidebar` | Navigation and browsing (database explorer, saved queries) |
|
||||
| **Editor** | `sqllab.editor` | SQL query editor workspace |
|
||||
| **Right Sidebar** | `sqllab.rightSidebar` | Contextual tools (AI assistants, query analysis) |
|
||||
| **Panels** | `sqllab.panels` | Results and related views (visualizations, data profiling) |
|
||||
| **Status Bar** | `sqllab.statusBar` | Connection status and query metrics |
|
||||
| Extension Point | ID | Views | Menus | Description |
|
||||
| ----------------- | --------------------- | ----- | ----- | ---------------------------------------------- |
|
||||
| **Left Sidebar** | `sqllab.leftSidebar` | — | ✓ | Menu actions for the database explorer |
|
||||
| **Editor** | `sqllab.editor` | ✓\* | ✓ | Custom editors + toolbar actions |
|
||||
| **Right Sidebar** | `sqllab.rightSidebar` | ✓ | — | Custom panels (AI assistants, query analysis) |
|
||||
| **Panels** | `sqllab.panels` | ✓ | ✓ | Custom tabs + toolbar actions (data profiling) |
|
||||
|
||||
## Area Customizations
|
||||
\*Editor views are contributed via [Editor Contributions](./editors), not standard view contributions.
|
||||
|
||||
Each extension point area supports three types of action customizations:
|
||||
## Customization Types
|
||||
|
||||
### Views
|
||||
|
||||
Extensions can add custom views (React components) to **Right Sidebar** and **Panels**. Views appear as new panels or tabs in their respective areas.
|
||||
|
||||
### Menus
|
||||
|
||||
Extensions can add toolbar actions to **Left Sidebar**, **Editor**, and **Panels**. Menu contributions support:
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ Area Title [Button] [Button] [•••] │
|
||||
│ [Button] [Button] [•••] │
|
||||
├───────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ │
|
||||
│ Area Content │
|
||||
│ │
|
||||
│ (right-click for context menu) │
|
||||
│ │
|
||||
│ │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Action Type | Location | Use Case |
|
||||
| --------------------- | ----------------- | ----------------------------------------------------- |
|
||||
| **Primary Actions** | Top-right buttons | Frequently used actions (e.g., run, refresh, add new) |
|
||||
| **Secondary Actions** | 3-dot menu (•••) | Less common actions (e.g., export, settings) |
|
||||
| **Context Actions** | Right-click menu | Context-sensitive actions on content |
|
||||
| Action Type | Location | Use Case |
|
||||
| --------------------- | ---------------- | ----------------------------------------------------- |
|
||||
| **Primary Actions** | Toolbar buttons | Frequently used actions (e.g., run, refresh, add new) |
|
||||
| **Secondary Actions** | 3-dot menu (•••) | Less common actions (e.g., export, settings) |
|
||||
|
||||
### Custom Editors
|
||||
|
||||
Extensions can replace the default SQL editor with custom implementations (Monaco, CodeMirror, etc.). See [Editor Contributions](./editors) for details.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -171,32 +173,38 @@ import { commands, sqlLab } from '@apache-superset/core';
|
||||
|
||||
export function activate(context) {
|
||||
// Register the commands declared in extension.json
|
||||
const formatCommand = commands.registerCommand('query_tools.format', {
|
||||
execute: () => {
|
||||
const formatCommand = commands.registerCommand(
|
||||
'query_tools.format',
|
||||
async () => {
|
||||
const tab = sqlLab.getCurrentTab();
|
||||
if (tab?.editor) {
|
||||
if (tab) {
|
||||
const editor = await tab.getEditor();
|
||||
// Format the SQL query
|
||||
}
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
const explainCommand = commands.registerCommand('query_tools.explain', {
|
||||
execute: () => {
|
||||
const explainCommand = commands.registerCommand(
|
||||
'query_tools.explain',
|
||||
async () => {
|
||||
const tab = sqlLab.getCurrentTab();
|
||||
if (tab?.editor) {
|
||||
if (tab) {
|
||||
const editor = await tab.getEditor();
|
||||
// Show query explanation
|
||||
}
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
const copyAsCteCommand = commands.registerCommand('query_tools.copy_as_cte', {
|
||||
execute: () => {
|
||||
const copyAsCteCommand = commands.registerCommand(
|
||||
'query_tools.copy_as_cte',
|
||||
async () => {
|
||||
const tab = sqlLab.getCurrentTab();
|
||||
if (tab?.editor) {
|
||||
if (tab) {
|
||||
const editor = await tab.getEditor();
|
||||
// Copy selected text as CTE
|
||||
}
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
context.subscriptions.push(formatCommand, explainCommand, copyAsCteCommand);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,63 @@
|
||||
---
|
||||
sidebar_position: 9
|
||||
title: Frequently Asked Questions
|
||||
description: Common questions about Apache Superset including performance, database support, visualizations, and configuration.
|
||||
keywords: [superset faq, superset questions, superset help, data visualization faq]
|
||||
---
|
||||
|
||||
import FAQSchema from '@site/src/components/FAQSchema';
|
||||
|
||||
<FAQSchema faqs={[
|
||||
{
|
||||
question: "How big of a dataset can Superset handle?",
|
||||
answer: "Superset can work with even gigantic databases. Superset acts as a thin layer above your underlying databases or data engines, which do all the processing. Superset simply visualizes the results of the query. The key to achieving acceptable performance is whether your database can execute queries and return results at acceptable speed."
|
||||
},
|
||||
{
|
||||
question: "What are the computing specifications required to run Superset?",
|
||||
answer: "The specs depend on how many users you have and their activity, not on the size of your data. Community members have reported 8GB RAM, 2vCPUs as adequate for a moderately-sized instance. Monitor your resource usage and adjust as needed."
|
||||
},
|
||||
{
|
||||
question: "Can I join or query multiple tables at one time?",
|
||||
answer: "Not in the Explore or Visualization UI directly. A Superset SQLAlchemy datasource can only be a single table or a view. You can create a view that joins tables, or use SQL Lab where you can write SQL queries to join multiple tables."
|
||||
},
|
||||
{
|
||||
question: "How do I create my own visualization?",
|
||||
answer: "Read the instructions in the Creating Visualization Plugins documentation to learn how to build custom visualizations for Superset."
|
||||
},
|
||||
{
|
||||
question: "Can I upload and visualize CSV data?",
|
||||
answer: "Yes! Superset supports CSV upload functionality. Read the Exploring Data documentation to learn how to enable and use CSV upload."
|
||||
},
|
||||
{
|
||||
question: "Why are my queries timing out?",
|
||||
answer: "There are many possible causes. For SQL Lab, Superset allows queries to run up to 6 hours by default (configurable via SQLLAB_ASYNC_TIME_LIMIT_SEC). For dashboard timeouts, check your gateway/proxy timeout settings and adjust SUPERSET_WEBSERVER_TIMEOUT in superset_config.py."
|
||||
},
|
||||
{
|
||||
question: "Why is the map not visible in the geospatial visualization?",
|
||||
answer: "You need to register a free account at Mapbox.com, obtain an API key, and add it to your .env file at the key MAPBOX_API_KEY."
|
||||
},
|
||||
{
|
||||
question: "What database engine can I use as a backend for Superset?",
|
||||
answer: "Superset is tested using MySQL, PostgreSQL, and SQLite backends for storing its internal metadata. While Superset supports many databases as data sources, only these are recommended for the metadata store in production."
|
||||
},
|
||||
{
|
||||
question: "Does Superset work with my database?",
|
||||
answer: "Superset supports any database with a Python SQLAlchemy dialect and DBAPI driver. Check the Connecting to Databases documentation for the full list of supported databases."
|
||||
},
|
||||
{
|
||||
question: "Does Superset offer a public API?",
|
||||
answer: "Yes, Superset has a public REST API documented using Swagger. Enable FAB_API_SWAGGER_UI in superset_config.py to access interactive API documentation at /swagger/v1."
|
||||
},
|
||||
{
|
||||
question: "Does Superset collect any telemetry data?",
|
||||
answer: "Superset uses Scarf by default to collect basic telemetry data to help maintainers understand version usage. Users can opt out by setting the SCARF_ANALYTICS environment variable to false."
|
||||
},
|
||||
{
|
||||
question: "Does Superset have a trash bin to recover deleted assets?",
|
||||
answer: "No, there is no built-in way to recover deleted dashboards, charts, or datasets. It is recommended to take periodic backups of the metadata database and use export functionality for recovery."
|
||||
}
|
||||
]} />
|
||||
|
||||
# FAQ
|
||||
|
||||
## How big of a dataset can Superset handle?
|
||||
|
||||
@@ -23,6 +23,7 @@ import type * as OpenApiPlugin from 'docusaurus-plugin-openapi-docs';
|
||||
import { themes } from 'prism-react-renderer';
|
||||
import remarkImportPartial from 'remark-import-partial';
|
||||
import remarkLocalizeBadges from './plugins/remark-localize-badges.mjs';
|
||||
import remarkTechArticleSchema from './plugins/remark-tech-article-schema.mjs';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
@@ -46,7 +47,7 @@ if (!versionsConfig.components.disabled) {
|
||||
sidebarPath: require.resolve('./sidebarComponents.js'),
|
||||
editUrl:
|
||||
'https://github.com/apache/superset/edit/master/docs/components',
|
||||
remarkPlugins: [remarkImportPartial, remarkLocalizeBadges],
|
||||
remarkPlugins: [remarkImportPartial, remarkLocalizeBadges, remarkTechArticleSchema],
|
||||
admonitions: {
|
||||
keywords: ['note', 'tip', 'info', 'warning', 'danger', 'resources'],
|
||||
extendDefaults: true,
|
||||
@@ -74,7 +75,7 @@ if (!versionsConfig.developer_portal.disabled) {
|
||||
sidebarPath: require.resolve('./sidebarTutorials.js'),
|
||||
editUrl:
|
||||
'https://github.com/apache/superset/edit/master/docs/developer_portal',
|
||||
remarkPlugins: [remarkImportPartial, remarkLocalizeBadges],
|
||||
remarkPlugins: [remarkImportPartial, remarkLocalizeBadges, remarkTechArticleSchema],
|
||||
admonitions: {
|
||||
keywords: ['note', 'tip', 'info', 'warning', 'danger', 'resources'],
|
||||
extendDefaults: true,
|
||||
@@ -171,13 +172,92 @@ const config: Config = {
|
||||
url: 'https://superset.apache.org',
|
||||
baseUrl: '/',
|
||||
onBrokenLinks: 'warn',
|
||||
onBrokenMarkdownLinks: 'throw',
|
||||
markdown: {
|
||||
mermaid: true,
|
||||
hooks: {
|
||||
onBrokenMarkdownLinks: 'throw',
|
||||
},
|
||||
},
|
||||
favicon: '/img/favicon.ico',
|
||||
organizationName: 'apache',
|
||||
projectName: 'superset',
|
||||
|
||||
// SEO: Structured data (Organization, Software, WebSite with SearchAction)
|
||||
headTags: [
|
||||
// SoftwareApplication schema
|
||||
{
|
||||
tagName: 'script',
|
||||
attributes: {
|
||||
type: 'application/ld+json',
|
||||
},
|
||||
innerHTML: JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'Apache Superset',
|
||||
applicationCategory: 'BusinessApplication',
|
||||
operatingSystem: 'Cross-platform',
|
||||
description: 'Apache Superset is a modern, enterprise-ready business intelligence web application for data exploration and visualization.',
|
||||
url: 'https://superset.apache.org',
|
||||
license: 'https://www.apache.org/licenses/LICENSE-2.0',
|
||||
author: {
|
||||
'@type': 'Organization',
|
||||
name: 'Apache Software Foundation',
|
||||
url: 'https://www.apache.org/',
|
||||
logo: 'https://www.apache.org/foundation/press/kit/asf_logo.png',
|
||||
},
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
price: '0',
|
||||
priceCurrency: 'USD',
|
||||
},
|
||||
featureList: [
|
||||
'Interactive dashboards',
|
||||
'SQL IDE',
|
||||
'40+ visualization types',
|
||||
'Semantic layer',
|
||||
'Role-based access control',
|
||||
'REST API',
|
||||
],
|
||||
}),
|
||||
},
|
||||
// WebSite schema with SearchAction (enables sitelinks search box in Google)
|
||||
{
|
||||
tagName: 'script',
|
||||
attributes: {
|
||||
type: 'application/ld+json',
|
||||
},
|
||||
innerHTML: JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebSite',
|
||||
name: 'Apache Superset',
|
||||
url: 'https://superset.apache.org',
|
||||
potentialAction: {
|
||||
'@type': 'SearchAction',
|
||||
target: {
|
||||
'@type': 'EntryPoint',
|
||||
urlTemplate: 'https://superset.apache.org/search?q={search_term_string}',
|
||||
},
|
||||
'query-input': 'required name=search_term_string',
|
||||
},
|
||||
}),
|
||||
},
|
||||
// Preconnect hints for faster external resource loading
|
||||
{
|
||||
tagName: 'link',
|
||||
attributes: {
|
||||
rel: 'preconnect',
|
||||
href: 'https://WR5FASX5ED-dsn.algolia.net',
|
||||
crossorigin: 'anonymous',
|
||||
},
|
||||
},
|
||||
{
|
||||
tagName: 'link',
|
||||
attributes: {
|
||||
rel: 'preconnect',
|
||||
href: 'https://analytics.apache.org',
|
||||
},
|
||||
},
|
||||
],
|
||||
themes: [
|
||||
'@saucelabs/theme-github-codeblock',
|
||||
'@docusaurus/theme-mermaid',
|
||||
@@ -186,14 +266,6 @@ const config: Config = {
|
||||
],
|
||||
plugins: [
|
||||
require.resolve('./src/webpack.extend.ts'),
|
||||
[
|
||||
'docusaurus-plugin-less',
|
||||
{
|
||||
lessOptions: {
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
...dynamicPlugins,
|
||||
[
|
||||
'docusaurus-plugin-openapi-docs',
|
||||
@@ -218,6 +290,19 @@ const config: Config = {
|
||||
},
|
||||
},
|
||||
],
|
||||
// SEO: Generate robots.txt during build
|
||||
[
|
||||
require.resolve('./plugins/robots-txt-plugin.js'),
|
||||
{
|
||||
policies: [
|
||||
{
|
||||
userAgent: '*',
|
||||
allow: '/',
|
||||
disallow: ['/api/v1/', '/_next/', '/static/js/*.map'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
'@docusaurus/plugin-client-redirects',
|
||||
{
|
||||
@@ -379,7 +464,7 @@ const config: Config = {
|
||||
}
|
||||
return `https://github.com/apache/superset/edit/master/docs/${versionDocsDirPath}/${docPath}`;
|
||||
},
|
||||
remarkPlugins: [remarkImportPartial, remarkLocalizeBadges],
|
||||
remarkPlugins: [remarkImportPartial, remarkLocalizeBadges, remarkTechArticleSchema],
|
||||
admonitions: {
|
||||
keywords: ['note', 'tip', 'info', 'warning', 'danger', 'resources'],
|
||||
extendDefaults: true,
|
||||
@@ -402,11 +487,57 @@ const config: Config = {
|
||||
theme: {
|
||||
customCss: require.resolve('./src/styles/custom.css'),
|
||||
},
|
||||
// SEO: Sitemap configuration with priorities
|
||||
sitemap: {
|
||||
lastmod: 'date',
|
||||
changefreq: 'weekly',
|
||||
priority: 0.5,
|
||||
ignorePatterns: ['/tags/**'],
|
||||
filename: 'sitemap.xml',
|
||||
createSitemapItems: async (params) => {
|
||||
const { defaultCreateSitemapItems, ...rest } = params;
|
||||
const items = await defaultCreateSitemapItems(rest);
|
||||
return items.map((item) => {
|
||||
// Boost priority for key pages
|
||||
if (item.url.includes('/docs/intro')) {
|
||||
return { ...item, priority: 1.0, changefreq: 'daily' };
|
||||
}
|
||||
if (item.url.includes('/docs/quickstart')) {
|
||||
return { ...item, priority: 0.9, changefreq: 'weekly' };
|
||||
}
|
||||
if (item.url.includes('/docs/installation/')) {
|
||||
return { ...item, priority: 0.8, changefreq: 'weekly' };
|
||||
}
|
||||
if (item.url.includes('/docs/databases')) {
|
||||
return { ...item, priority: 0.8, changefreq: 'weekly' };
|
||||
}
|
||||
if (item.url.includes('/docs/faq')) {
|
||||
return { ...item, priority: 0.7, changefreq: 'monthly' };
|
||||
}
|
||||
if (item.url === 'https://superset.apache.org/') {
|
||||
return { ...item, priority: 1.0, changefreq: 'daily' };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
},
|
||||
},
|
||||
} satisfies Options,
|
||||
],
|
||||
],
|
||||
|
||||
themeConfig: {
|
||||
// SEO: OpenGraph and Twitter meta tags
|
||||
metadata: [
|
||||
{ name: 'keywords', content: 'data visualization, business intelligence, BI, dashboards, SQL, analytics, open source, Apache, charts, reporting' },
|
||||
{ property: 'og:type', content: 'website' },
|
||||
{ property: 'og:site_name', content: 'Apache Superset' },
|
||||
{ property: 'og:image', content: 'https://superset.apache.org/img/superset-og-image.png' },
|
||||
{ property: 'og:image:width', content: '1200' },
|
||||
{ property: 'og:image:height', content: '630' },
|
||||
{ name: 'twitter:card', content: 'summary_large_image' },
|
||||
{ name: 'twitter:image', content: 'https://superset.apache.org/img/superset-og-image.png' },
|
||||
{ name: 'twitter:site', content: '@ApacheSuperset' },
|
||||
],
|
||||
colorMode: {
|
||||
defaultMode: 'dark',
|
||||
disableSwitch: false,
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
# Yarn version
|
||||
YARN_VERSION = "1.22.22"
|
||||
# Increase heap size for webpack bundling of Superset UI components
|
||||
NODE_OPTIONS = "--max-old-space-size=4096"
|
||||
NODE_OPTIONS = "--max-old-space-size=8192"
|
||||
|
||||
# Deploy preview settings
|
||||
[context.deploy-preview]
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"_init": "cat src/intro_header.txt ../README.md > docs/intro.md",
|
||||
"start": "yarn run _init && yarn run generate:all && NODE_ENV=development docusaurus start",
|
||||
"start": "yarn run _init && yarn run generate:all && NODE_OPTIONS='--max-old-space-size=8192' NODE_ENV=development docusaurus start",
|
||||
"start:quick": "yarn run _init && NODE_OPTIONS='--max-old-space-size=8192' NODE_ENV=development docusaurus start",
|
||||
"stop": "pkill -f 'docusaurus start' || pkill -f 'docusaurus serve' || echo 'No docusaurus server running'",
|
||||
"build": "yarn run _init && yarn run generate:all && DEBUG=docusaurus:* docusaurus build",
|
||||
"build": "yarn run _init && yarn run generate:all && NODE_OPTIONS='--max-old-space-size=8192' DEBUG=docusaurus:* docusaurus build",
|
||||
"generate:api-docs": "python3 scripts/fix-openapi-spec.py && docusaurus gen-api-docs superset && node scripts/convert-api-sidebar.mjs && node scripts/generate-api-index.mjs && node scripts/generate-api-tag-pages.mjs",
|
||||
"clean:api-docs": "docusaurus clean-api-docs superset",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
@@ -22,7 +23,7 @@
|
||||
"generate:superset-components": "node scripts/generate-superset-components.mjs",
|
||||
"generate:database-docs": "node scripts/generate-database-docs.mjs",
|
||||
"gen-db-docs": "node scripts/generate-database-docs.mjs",
|
||||
"generate:all": "yarn run generate:extension-components && yarn run generate:superset-components && yarn run generate:database-docs && yarn run generate:api-docs",
|
||||
"generate:all": "yarn run generate:extension-components & yarn run generate:superset-components & yarn run generate:database-docs & wait && yarn run generate:api-docs",
|
||||
"lint:db-metadata": "python3 ../superset/db_engine_specs/lint_metadata.py",
|
||||
"lint:db-metadata:report": "python3 ../superset/db_engine_specs/lint_metadata.py --markdown -o ../superset/db_engine_specs/METADATA_STATUS.md",
|
||||
"update:readme-db-logos": "node scripts/generate-database-docs.mjs --update-readme",
|
||||
@@ -38,15 +39,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.1.0",
|
||||
"@babel/core": "^7.26.0",
|
||||
"@babel/preset-react": "^7.26.3",
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@docusaurus/core": "3.9.2",
|
||||
"@docusaurus/plugin-client-redirects": "3.9.2",
|
||||
"@docusaurus/preset-classic": "3.9.2",
|
||||
"@docusaurus/theme-live-codeblock": "^3.9.2",
|
||||
"@docusaurus/theme-mermaid": "^3.9.2",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/core": "^11.0.0",
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
@@ -55,41 +52,39 @@
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@saucelabs/theme-github-codeblock": "^0.3.0",
|
||||
"@storybook/addon-docs": "^8.6.15",
|
||||
"@storybook/blocks": "^8.6.11",
|
||||
"@storybook/channels": "^8.6.11",
|
||||
"@storybook/client-logger": "^8.6.11",
|
||||
"@storybook/components": "^8.6.11",
|
||||
"@storybook/core": "^8.6.11",
|
||||
"@storybook/core-events": "^8.6.11",
|
||||
"@storybook/blocks": "^8.6.15",
|
||||
"@storybook/channels": "^8.6.15",
|
||||
"@storybook/client-logger": "^8.6.15",
|
||||
"@storybook/components": "^8.6.15",
|
||||
"@storybook/core": "^8.6.15",
|
||||
"@storybook/core-events": "^8.6.15",
|
||||
"@storybook/csf": "^0.1.13",
|
||||
"@storybook/docs-tools": "^8.6.11",
|
||||
"@storybook/preview-api": "^8.6.11",
|
||||
"@storybook/theming": "^8.6.11",
|
||||
"@storybook/docs-tools": "^8.6.15",
|
||||
"@storybook/preview-api": "^8.6.15",
|
||||
"@storybook/theming": "^8.6.15",
|
||||
"@superset-ui/core": "^0.20.4",
|
||||
"antd": "^6.2.2",
|
||||
"babel-loader": "^9.2.1",
|
||||
"caniuse-lite": "^1.0.30001766",
|
||||
"docusaurus-plugin-less": "^2.0.2",
|
||||
"@swc/core": "^1.15.11",
|
||||
"antd": "^6.2.3",
|
||||
"baseline-browser-mapping": "^2.9.19",
|
||||
"caniuse-lite": "^1.0.30001769",
|
||||
"docusaurus-plugin-openapi-docs": "^4.6.0",
|
||||
"docusaurus-theme-openapi-docs": "^4.6.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"js-yaml-loader": "^1.2.2",
|
||||
"json-bigint": "^1.0.0",
|
||||
"less": "^4.5.1",
|
||||
"less-loader": "^12.3.0",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-github-btn": "^1.4.0",
|
||||
"react-resize-detector": "7.1.2",
|
||||
"react-resize-detector": "^9.1.1",
|
||||
"react-svg-pan-zoom": "^3.13.1",
|
||||
"react-table": "^7.8.0",
|
||||
"remark-import-partial": "^0.0.2",
|
||||
"reselect": "^5.1.1",
|
||||
"storybook": "^8.6.15",
|
||||
"swagger-ui-react": "^5.31.0",
|
||||
"swc-loader": "^0.2.7",
|
||||
"tinycolor2": "^1.4.2",
|
||||
"ts-loader": "^9.5.4",
|
||||
"unist-util-visit": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -104,11 +99,11 @@
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^17.2.0",
|
||||
"globals": "^17.3.0",
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.54.0",
|
||||
"webpack": "^5.104.1"
|
||||
"webpack": "^5.105.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@@ -124,7 +119,8 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"react-redux": "^9.2.0",
|
||||
"@reduxjs/toolkit": "^2.5.0"
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"baseline-browser-mapping": "^2.9.19"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
|
||||
}
|
||||
|
||||
153
docs/plugins/remark-tech-article-schema.mjs
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Note: visit from unist-util-visit is available if needed for tree traversal
|
||||
|
||||
/**
|
||||
* Remark plugin that automatically injects TechArticle schema import and component
|
||||
* into documentation MDX files based on frontmatter.
|
||||
*
|
||||
* This enables rich snippets for technical documentation in search results.
|
||||
*
|
||||
* Frontmatter options:
|
||||
* - title: (required) Article headline
|
||||
* - description: (required) Article description
|
||||
* - keywords: (optional) Array of keywords
|
||||
* - seo_proficiency: (optional) 'Beginner' or 'Expert', defaults to 'Beginner'
|
||||
* - seo_schema: (optional) Set to false to disable schema injection
|
||||
*/
|
||||
export default function remarkTechArticleSchema() {
|
||||
return (tree, file) => {
|
||||
const frontmatter = file.data.frontMatter || {};
|
||||
|
||||
// Skip if explicitly disabled or missing required fields
|
||||
if (frontmatter.seo_schema === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only add schema if we have title and description
|
||||
if (!frontmatter.title || !frontmatter.description) {
|
||||
return;
|
||||
}
|
||||
|
||||
const title = frontmatter.title;
|
||||
const description = frontmatter.description;
|
||||
const keywords = Array.isArray(frontmatter.keywords) ? frontmatter.keywords : [];
|
||||
const proficiencyLevel = frontmatter.seo_proficiency || 'Beginner';
|
||||
|
||||
// Create the import statement
|
||||
const importNode = {
|
||||
type: 'mdxjsEsm',
|
||||
value: `import TechArticleSchema from '@site/src/components/TechArticleSchema';`,
|
||||
data: {
|
||||
estree: {
|
||||
type: 'Program',
|
||||
sourceType: 'module',
|
||||
body: [
|
||||
{
|
||||
type: 'ImportDeclaration',
|
||||
specifiers: [
|
||||
{
|
||||
type: 'ImportDefaultSpecifier',
|
||||
local: { type: 'Identifier', name: 'TechArticleSchema' },
|
||||
},
|
||||
],
|
||||
source: {
|
||||
type: 'Literal',
|
||||
value: '@site/src/components/TechArticleSchema',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Create the component node for MDX
|
||||
const componentNode = {
|
||||
type: 'mdxJsxFlowElement',
|
||||
name: 'TechArticleSchema',
|
||||
attributes: [
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'title',
|
||||
value: title,
|
||||
},
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'description',
|
||||
value: description,
|
||||
},
|
||||
...(keywords.length > 0
|
||||
? [
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'keywords',
|
||||
value: {
|
||||
type: 'mdxJsxAttributeValueExpression',
|
||||
value: JSON.stringify(keywords),
|
||||
data: {
|
||||
estree: {
|
||||
type: 'Program',
|
||||
sourceType: 'module',
|
||||
body: [
|
||||
{
|
||||
type: 'ExpressionStatement',
|
||||
expression: {
|
||||
type: 'ArrayExpression',
|
||||
elements: keywords.map((k) => ({
|
||||
type: 'Literal',
|
||||
value: k,
|
||||
})),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(proficiencyLevel !== 'Beginner'
|
||||
? [
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'proficiencyLevel',
|
||||
value: proficiencyLevel,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
children: [],
|
||||
};
|
||||
|
||||
// Insert import at the beginning
|
||||
tree.children.unshift(importNode);
|
||||
|
||||
// Find the first heading and insert component after it
|
||||
let insertIndex = 1; // Default: after import
|
||||
for (let i = 1; i < tree.children.length; i++) {
|
||||
if (tree.children[i].type === 'heading') {
|
||||
insertIndex = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tree.children.splice(insertIndex, 0, componentNode);
|
||||
};
|
||||
}
|
||||
83
docs/plugins/robots-txt-plugin.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||
|
||||
/**
|
||||
* Docusaurus plugin to generate robots.txt during build
|
||||
* Configuration is passed via plugin options
|
||||
*/
|
||||
module.exports = function robotsTxtPlugin(context, options = {}) {
|
||||
const { siteConfig } = context;
|
||||
const {
|
||||
policies = [{ userAgent: '*', allow: '/' }],
|
||||
additionalSitemaps = [],
|
||||
} = options;
|
||||
|
||||
return {
|
||||
name: 'robots-txt-plugin',
|
||||
|
||||
async postBuild({ outDir }) {
|
||||
const sitemapUrl = `${siteConfig.url}/sitemap.xml`;
|
||||
|
||||
// Build robots.txt content
|
||||
const lines = [];
|
||||
|
||||
// Add policies
|
||||
for (const policy of policies) {
|
||||
lines.push(`User-agent: ${policy.userAgent}`);
|
||||
|
||||
if (policy.allow) {
|
||||
const allows = Array.isArray(policy.allow) ? policy.allow : [policy.allow];
|
||||
for (const allow of allows) {
|
||||
lines.push(`Allow: ${allow}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (policy.disallow) {
|
||||
const disallows = Array.isArray(policy.disallow) ? policy.disallow : [policy.disallow];
|
||||
for (const disallow of disallows) {
|
||||
lines.push(`Disallow: ${disallow}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (policy.crawlDelay) {
|
||||
lines.push(`Crawl-delay: ${policy.crawlDelay}`);
|
||||
}
|
||||
|
||||
lines.push(''); // Empty line between policies
|
||||
}
|
||||
|
||||
// Add sitemaps
|
||||
lines.push(`Sitemap: ${sitemapUrl}`);
|
||||
for (const sitemap of additionalSitemaps) {
|
||||
lines.push(`Sitemap: ${sitemap}`);
|
||||
}
|
||||
|
||||
// Write robots.txt
|
||||
const robotsPath = path.join(outDir, 'robots.txt');
|
||||
fs.writeFileSync(robotsPath, lines.join('\n'));
|
||||
|
||||
console.log('Generated robots.txt');
|
||||
},
|
||||
};
|
||||
};
|
||||
66
docs/src/components/FAQSchema.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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 type { JSX } from 'react';
|
||||
import Head from '@docusaurus/Head';
|
||||
|
||||
interface FAQItem {
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
interface FAQSchemaProps {
|
||||
faqs: FAQItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that injects FAQPage JSON-LD structured data
|
||||
* Use this on FAQ pages to enable rich snippets in search results
|
||||
*
|
||||
* @example
|
||||
* <FAQSchema faqs={[
|
||||
* { question: "What is Superset?", answer: "Apache Superset is..." },
|
||||
* { question: "How do I install it?", answer: "You can install via..." }
|
||||
* ]} />
|
||||
*/
|
||||
export default function FAQSchema({ faqs }: FAQSchemaProps): JSX.Element | null {
|
||||
// FAQPage schema requires a non-empty mainEntity array per schema.org specs
|
||||
if (!faqs || faqs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: faqs.map((faq) => ({
|
||||
'@type': 'Question',
|
||||
name: faq.question,
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: faq.answer,
|
||||
},
|
||||
})),
|
||||
};
|
||||
|
||||
return (
|
||||
<Head>
|
||||
<script type="application/ld+json">{JSON.stringify(schema)}</script>
|
||||
</Head>
|
||||
);
|
||||
}
|
||||
91
docs/src/components/TechArticleSchema.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 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 type { JSX } from 'react';
|
||||
import Head from '@docusaurus/Head';
|
||||
import { useLocation } from '@docusaurus/router';
|
||||
|
||||
interface TechArticleSchemaProps {
|
||||
title: string;
|
||||
description: string;
|
||||
datePublished?: string;
|
||||
dateModified?: string;
|
||||
keywords?: string[];
|
||||
proficiencyLevel?: 'Beginner' | 'Expert';
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that injects TechArticle JSON-LD structured data for documentation pages.
|
||||
* This helps search engines understand technical documentation content.
|
||||
*
|
||||
* @example
|
||||
* <TechArticleSchema
|
||||
* title="Installing Superset with Docker"
|
||||
* description="Learn how to install Apache Superset using Docker Compose"
|
||||
* keywords={['docker', 'installation', 'superset']}
|
||||
* proficiencyLevel="Beginner"
|
||||
* />
|
||||
*/
|
||||
export default function TechArticleSchema({
|
||||
title,
|
||||
description,
|
||||
datePublished,
|
||||
dateModified,
|
||||
keywords = [],
|
||||
proficiencyLevel = 'Beginner',
|
||||
}: TechArticleSchemaProps): JSX.Element {
|
||||
const location = useLocation();
|
||||
const url = `https://superset.apache.org${location.pathname}`;
|
||||
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'TechArticle',
|
||||
headline: title,
|
||||
description,
|
||||
url,
|
||||
proficiencyLevel,
|
||||
author: {
|
||||
'@type': 'Organization',
|
||||
name: 'Apache Superset Contributors',
|
||||
url: 'https://github.com/apache/superset/graphs/contributors',
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'Apache Software Foundation',
|
||||
url: 'https://www.apache.org/',
|
||||
logo: {
|
||||
'@type': 'ImageObject',
|
||||
url: 'https://www.apache.org/foundation/press/kit/asf_logo.png',
|
||||
},
|
||||
},
|
||||
mainEntityOfPage: {
|
||||
'@type': 'WebPage',
|
||||
'@id': url,
|
||||
},
|
||||
...(datePublished && { datePublished }),
|
||||
...(dateModified && { dateModified }),
|
||||
...(keywords.length > 0 && { keywords: keywords.join(', ') }),
|
||||
};
|
||||
|
||||
return (
|
||||
<Head>
|
||||
<script type="application/ld+json">{JSON.stringify(schema)}</script>
|
||||
</Head>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"generated": "2026-01-27T23:17:43.310Z",
|
||||
"generated": "2026-01-31T10:47:01.730Z",
|
||||
"statistics": {
|
||||
"totalDatabases": 68,
|
||||
"withDocumentation": 68,
|
||||
"withConnectionString": 68,
|
||||
"withDrivers": 35,
|
||||
"totalDatabases": 70,
|
||||
"withDocumentation": 70,
|
||||
"withConnectionString": 70,
|
||||
"withDrivers": 36,
|
||||
"withAuthMethods": 4,
|
||||
"supportsJoins": 64,
|
||||
"supportsSubqueries": 65,
|
||||
"supportsJoins": 66,
|
||||
"supportsSubqueries": 67,
|
||||
"supportsDynamicSchema": 15,
|
||||
"supportsCatalog": 9,
|
||||
"averageScore": 33,
|
||||
"averageScore": 32,
|
||||
"maxScore": 201,
|
||||
"byCategory": {
|
||||
"Other Databases": [
|
||||
@@ -109,6 +109,8 @@
|
||||
"Traditional RDBMS": [
|
||||
"Aurora MySQL (Data API)",
|
||||
"Aurora PostgreSQL (Data API)",
|
||||
"Aurora MySQL",
|
||||
"Aurora PostgreSQL",
|
||||
"CockroachDB",
|
||||
"Cloudflare D1",
|
||||
"IBM Db2",
|
||||
@@ -133,6 +135,8 @@
|
||||
"Open Source": [
|
||||
"Aurora MySQL (Data API)",
|
||||
"Aurora PostgreSQL (Data API)",
|
||||
"Aurora MySQL",
|
||||
"Aurora PostgreSQL",
|
||||
"ClickHouse",
|
||||
"CockroachDB",
|
||||
"Couchbase",
|
||||
@@ -490,6 +494,132 @@
|
||||
"query_cost_estimation": false,
|
||||
"sql_validation": false
|
||||
},
|
||||
"Aurora MySQL": {
|
||||
"engine": "aurora_mysql",
|
||||
"engine_name": "Aurora MySQL",
|
||||
"module": "aurora",
|
||||
"documentation": {
|
||||
"description": "MySQL is a popular open-source relational database.",
|
||||
"logo": "mysql.png",
|
||||
"homepage_url": "https://www.mysql.com/",
|
||||
"categories": [
|
||||
"TRADITIONAL_RDBMS",
|
||||
"OPEN_SOURCE"
|
||||
],
|
||||
"pypi_packages": [
|
||||
"mysqlclient"
|
||||
],
|
||||
"connection_string": "mysql://{username}:{password}@{host}/{database}",
|
||||
"default_port": 3306,
|
||||
"parameters": {
|
||||
"username": "Database username",
|
||||
"password": "Database password",
|
||||
"host": "localhost, 127.0.0.1, IP address, or hostname",
|
||||
"database": "Database name"
|
||||
},
|
||||
"host_examples": [
|
||||
{
|
||||
"platform": "Localhost",
|
||||
"host": "localhost or 127.0.0.1"
|
||||
},
|
||||
{
|
||||
"platform": "Docker on Linux",
|
||||
"host": "172.18.0.1"
|
||||
},
|
||||
{
|
||||
"platform": "Docker on macOS",
|
||||
"host": "docker.for.mac.host.internal"
|
||||
},
|
||||
{
|
||||
"platform": "On-premise",
|
||||
"host": "IP address or hostname"
|
||||
}
|
||||
],
|
||||
"drivers": [
|
||||
{
|
||||
"name": "mysqlclient",
|
||||
"pypi_package": "mysqlclient",
|
||||
"connection_string": "mysql://{username}:{password}@{host}/{database}",
|
||||
"is_recommended": true,
|
||||
"notes": "Recommended driver. May fail with caching_sha2_password auth."
|
||||
},
|
||||
{
|
||||
"name": "mysql-connector-python",
|
||||
"pypi_package": "mysql-connector-python",
|
||||
"connection_string": "mysql+mysqlconnector://{username}:{password}@{host}/{database}",
|
||||
"is_recommended": false,
|
||||
"notes": "Required for newer MySQL databases using caching_sha2_password authentication."
|
||||
}
|
||||
]
|
||||
},
|
||||
"time_grains": {},
|
||||
"score": 0,
|
||||
"max_score": 0,
|
||||
"joins": true,
|
||||
"subqueries": true,
|
||||
"supports_dynamic_schema": false,
|
||||
"supports_catalog": false,
|
||||
"supports_dynamic_catalog": false,
|
||||
"ssh_tunneling": false,
|
||||
"query_cancelation": false,
|
||||
"supports_file_upload": false,
|
||||
"user_impersonation": false,
|
||||
"query_cost_estimation": false,
|
||||
"sql_validation": false
|
||||
},
|
||||
"Aurora PostgreSQL": {
|
||||
"engine": "aurora_postgresql",
|
||||
"engine_name": "Aurora PostgreSQL",
|
||||
"module": "aurora",
|
||||
"documentation": {
|
||||
"description": "PostgreSQL is an advanced open-source relational database.",
|
||||
"logo": "postgresql.svg",
|
||||
"homepage_url": "https://www.postgresql.org/",
|
||||
"categories": [
|
||||
"TRADITIONAL_RDBMS",
|
||||
"OPEN_SOURCE"
|
||||
],
|
||||
"pypi_packages": [
|
||||
"psycopg2"
|
||||
],
|
||||
"connection_string": "postgresql://{username}:{password}@{host}:{port}/{database}",
|
||||
"default_port": 5432,
|
||||
"parameters": {
|
||||
"username": "Database username",
|
||||
"password": "Database password",
|
||||
"host": "For localhost: localhost or 127.0.0.1. For AWS: endpoint URL",
|
||||
"port": "Default 5432",
|
||||
"database": "Database name"
|
||||
},
|
||||
"notes": "The psycopg2 library comes bundled with Superset Docker images.",
|
||||
"connection_examples": [
|
||||
{
|
||||
"description": "Basic connection",
|
||||
"connection_string": "postgresql://{username}:{password}@{host}:{port}/{database}"
|
||||
},
|
||||
{
|
||||
"description": "With SSL required",
|
||||
"connection_string": "postgresql://{username}:{password}@{host}:{port}/{database}?sslmode=require"
|
||||
}
|
||||
],
|
||||
"docs_url": "https://www.postgresql.org/docs/",
|
||||
"sqlalchemy_docs_url": "https://docs.sqlalchemy.org/en/13/dialects/postgresql.html"
|
||||
},
|
||||
"time_grains": {},
|
||||
"score": 0,
|
||||
"max_score": 0,
|
||||
"joins": true,
|
||||
"subqueries": true,
|
||||
"supports_dynamic_schema": false,
|
||||
"supports_catalog": false,
|
||||
"supports_dynamic_catalog": false,
|
||||
"ssh_tunneling": false,
|
||||
"query_cancelation": false,
|
||||
"supports_file_upload": false,
|
||||
"user_impersonation": false,
|
||||
"query_cost_estimation": false,
|
||||
"sql_validation": false
|
||||
},
|
||||
"Google BigQuery": {
|
||||
"engine": "google_bigquery",
|
||||
"engine_name": "Google BigQuery",
|
||||
|
||||
@@ -28,7 +28,7 @@ import databaseData from '../data/databases.json';
|
||||
import BlurredSection from '../components/BlurredSection';
|
||||
import DataSet from '../../../RESOURCES/INTHEWILD.yaml';
|
||||
import type { DatabaseData } from '../components/databases/types';
|
||||
import '../styles/main.less';
|
||||
import '../styles/main.css';
|
||||
|
||||
// Build database list from databases.json (databases with logos)
|
||||
// Deduplicate by logo filename to avoid showing the same logo twice
|
||||
@@ -795,7 +795,7 @@ export default function Home(): JSX.Element {
|
||||
</StyledIntegrations>
|
||||
</BlurredSection>
|
||||
{/* Only show carousel when we have enough logos (>10) for a good display */}
|
||||
{companiesWithLogos.length > 10 && (
|
||||
{companiesWithLogos.length > 7 && (
|
||||
<BlurredSection>
|
||||
<div style={{ padding: '0 20px' }}>
|
||||
<SectionHeader
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
@import 'antd-theme.less';
|
||||
|
||||
body {
|
||||
font-family: var(--ifm-font-family-base);
|
||||
@@ -81,26 +80,29 @@ a > span > svg {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
&::before {
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(180deg, #11b0d8 0%, #116f86 100%);
|
||||
content: '';
|
||||
display: block;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
&:hover {
|
||||
color: #ffffff;
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.default-button-theme::before {
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(180deg, #11b0d8 0%, #116f86 100%);
|
||||
content: '';
|
||||
display: block;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.default-button-theme:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.default-button-theme:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Navbar */
|
||||
@@ -109,32 +111,32 @@ a > span > svg {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.get-started-button {
|
||||
border-radius: 10px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
width: 142px;
|
||||
padding: 7px 0;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.navbar .get-started-button {
|
||||
border-radius: 10px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
width: 142px;
|
||||
padding: 7px 0;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.github-button {
|
||||
background-image: url('/img/github.png');
|
||||
background-size: contain;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.navbar .github-button {
|
||||
background-image: url('/img/github.png');
|
||||
background-size: contain;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.navbar--dark {
|
||||
background-color: transparent;
|
||||
border-bottom: 1px solid rgba(24, 115, 132, 0.4);
|
||||
}
|
||||
|
||||
.github-button {
|
||||
background-image: url('/img/github-dark.png');
|
||||
}
|
||||
.navbar--dark .github-button {
|
||||
background-image: url('/img/github-dark.png');
|
||||
}
|
||||
|
||||
.navbar__logo {
|
||||
@@ -153,11 +155,11 @@ a > span > svg {
|
||||
.navbar {
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.get-started-button,
|
||||
.github-button {
|
||||
display: none;
|
||||
}
|
||||
.navbar .get-started-button,
|
||||
.navbar .github-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar__items {
|
||||
@@ -186,20 +188,20 @@ a > span > svg {
|
||||
--docsearch-searchbox-background: var(--ifm-navbar-background-color);
|
||||
border: 1px solid #187384;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
&.DocSearch-Button {
|
||||
width: 225px;
|
||||
}
|
||||
.navbar .DocSearch.DocSearch-Button {
|
||||
width: 225px;
|
||||
}
|
||||
|
||||
.DocSearch-Search-Icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.navbar .DocSearch .DocSearch-Search-Icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.DocSearch-Button-Key,
|
||||
.DocSearch-Button-Placeholder {
|
||||
display: none;
|
||||
}
|
||||
.navbar .DocSearch .DocSearch-Button-Key,
|
||||
.navbar .DocSearch .DocSearch-Button-Placeholder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar--dark .DocSearch {
|
||||
@@ -232,25 +234,25 @@ a > span > svg {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 13px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
.footer__ci-services span {
|
||||
font-size: 13px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
transition: opacity 0.2s;
|
||||
.footer__ci-services a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
.footer__ci-services a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 28px;
|
||||
}
|
||||
.footer__ci-services img {
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.footer__divider {
|
||||
@@ -268,13 +270,13 @@ a > span > svg {
|
||||
.footer__ci-services {
|
||||
gap: 12px;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
}
|
||||
.footer__ci-services span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 22px;
|
||||
}
|
||||
.footer__ci-services img {
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
@@ -67,8 +67,8 @@ export default function webpackExtendPlugin(): Plugin<void> {
|
||||
use: 'js-yaml-loader',
|
||||
});
|
||||
|
||||
// Add babel-loader rule for superset-frontend files
|
||||
// This ensures Emotion CSS-in-JS is processed correctly for SSG
|
||||
// Add swc-loader rule for superset-frontend files
|
||||
// SWC is a Rust-based transpiler that's significantly faster than babel
|
||||
const supersetFrontendPath = path.resolve(
|
||||
__dirname,
|
||||
'../../superset-frontend',
|
||||
@@ -76,26 +76,37 @@ export default function webpackExtendPlugin(): Plugin<void> {
|
||||
config.module?.rules?.push({
|
||||
test: /\.(tsx?|jsx?)$/,
|
||||
include: supersetFrontendPath,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
loader: 'swc-loader',
|
||||
options: {
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-react',
|
||||
{
|
||||
// Ignore superset-frontend/.swcrc which references plugins not
|
||||
// installed in the docs workspace (e.g. @swc/plugin-emotion)
|
||||
swcrc: false,
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
tsx: true,
|
||||
},
|
||||
transform: {
|
||||
react: {
|
||||
runtime: 'automatic',
|
||||
importSource: '@emotion/react',
|
||||
},
|
||||
],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
plugins: ['@emotion/babel-plugin'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
devtool: isDev ? 'eval-source-map' : config.devtool,
|
||||
devtool: isDev ? false : config.devtool,
|
||||
cache: {
|
||||
type: 'filesystem',
|
||||
buildDependencies: {
|
||||
config: [__filename],
|
||||
},
|
||||
},
|
||||
...(isDev && {
|
||||
optimization: {
|
||||
...config.optimization,
|
||||
@@ -208,8 +219,6 @@ export default function webpackExtendPlugin(): Plugin<void> {
|
||||
),
|
||||
},
|
||||
},
|
||||
// We're removing the ts-loader rule that was processing superset-frontend files
|
||||
// This will prevent TypeScript errors from files outside the docs directory
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
13
docs/static/feature-flags.json
vendored
@@ -114,6 +114,12 @@
|
||||
"lifecycle": "testing",
|
||||
"description": "Allow users to export full CSV of table viz type. Warning: Could cause server memory/compute issues with large datasets."
|
||||
},
|
||||
{
|
||||
"name": "AWS_DATABASE_IAM_AUTH",
|
||||
"default": false,
|
||||
"lifecycle": "testing",
|
||||
"description": "Enable AWS IAM authentication for database connections (Aurora, Redshift). Allows cross-account role assumption via STS AssumeRole. Security note: When enabled, ensure Superset's IAM role has restricted sts:AssumeRole permissions to prevent unauthorized access."
|
||||
},
|
||||
{
|
||||
"name": "CACHE_IMPERSONATION",
|
||||
"default": false,
|
||||
@@ -241,6 +247,13 @@
|
||||
"description": "Enables dashboard virtualization for improved performance",
|
||||
"category": "path_to_deprecation"
|
||||
},
|
||||
{
|
||||
"name": "DASHBOARD_VIRTUALIZATION_DEFER_DATA",
|
||||
"default": false,
|
||||
"lifecycle": "stable",
|
||||
"description": "Supports simultaneous data and dashboard virtualization for backend performance",
|
||||
"category": "runtime_config"
|
||||
},
|
||||
{
|
||||
"name": "DATAPANEL_CLOSED_BY_DEFAULT",
|
||||
"default": false,
|
||||
|
||||
BIN
docs/static/img/databases/alloydb.png
vendored
Normal file
|
After Width: | Height: | Size: 86 KiB |
57
docs/static/img/databases/apache-iotdb.svg
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1"
|
||||
id="Õ_xBA__x2264__x201E__1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 107.7 107.7"
|
||||
style="enable-background:new 0 0 107.7 107.7;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#9E2878;}
|
||||
</style>
|
||||
<g>
|
||||
<g id="g1133" transform="translate(-244.51235,-228.78793)">
|
||||
<path id="path1119" class="st0" d="M340.8,253.8c2.6-1,5.5,0.4,6.2,3c0.7,2.6-1.1,5.7-3.7,6.4c-3.2,0.6-6.2-1.4-9.3,0.2
|
||||
c-3.1,1.7-4,5.2-4.7,8.3c-1.4,5-8.5,7.3-12.1,4.2c-3.3-2.4-3.4-7.8-0.2-11c2.2-2.5,5.9-3.3,8.8-2c2.7,1.2,6,1.7,8.6-0.4
|
||||
C337.5,260.1,336.7,255.1,340.8,253.8L340.8,253.8z"/>
|
||||
<path id="path1121" class="st0" d="M280.5,244.7c4.2-2.2,9.5,1.5,8.2,6.1c-1.4,5.4-0.7,11.5,2.9,15.5c3.4,4,9.8,4.8,14.6,1.9
|
||||
c3.7-2.1,6-5.8,7.4-9.6c1-3.1,0.6-6.2,1.1-9.3c1-3.8,5.8-6,9.1-4.2c3.2,1.4,4,5.9,1.7,8.8c-1.4,2.2-4.2,2.7-6.3,3.8
|
||||
c-3.6,1.9-6.5,4.9-8.4,8.6c-1.2,2.1-1.1,4.5-1.7,6.7c-0.9,1.9-3,2.7-4.8,2.9c-4.8,0.6-9.5,3-13,6.7c-1.7,1.7-3.1,4.2-5.6,4.2
|
||||
c-2.7-0.2-4.7-2.6-7.4-2.7c-4.9-0.7-10.2,0.7-14.4,4c-3.7,3.1-9.4,0.8-9.6-3.7c-0.9-5.1,6.2-9.5,10.1-6.2c4.3,4,10.8,5.6,16.9,3.7
|
||||
c5.6-1.9,9.7-8.3,8.6-14c-0.9-4.9-4.2-9.3-8.6-11.4c-1.7-0.8-3.6-1.6-4.2-3.5C275.6,250.2,277.3,246.2,280.5,244.7L280.5,244.7z"
|
||||
/>
|
||||
<path id="path1123" class="st0" d="M277.8,235.9c2.2-0.8,4.2,1.3,3.3,3.4c-0.6,2.1-3.7,2.7-4.8,1.1
|
||||
C275,238.9,276,236.4,277.8,235.9z"/>
|
||||
<path id="path1125" class="st0" d="M246.9,278.8c2.2-0.8,4.2,1.2,3.3,3.4c-0.6,2.1-3.7,2.7-4.8,1C244,281.9,245,279.3,246.9,278.8
|
||||
L246.9,278.8z"/>
|
||||
<path id="path1127" class="st0" d="M328.2,236.2c2.2-0.7,4.2,1.3,3.3,3.5c-0.6,2-3.7,2.7-4.8,1
|
||||
C325.4,239.2,326.3,236.8,328.2,236.2z"/>
|
||||
<path id="path1129" class="st0" d="M253.6,257.7c0.4-3.7,5.5-5.9,8.1-3.6c1.9,1.1,1.6,3.6,2.2,5.4c0.4,2.4,2.7,4.3,5.2,4.3
|
||||
c3.2,0.3,6.4-2.3,9.5-1.2c4.8,1.2,6.5,7.5,3.2,11.5c-2.9,4.1-9.3,4.5-12,0.7c-2.4-2.8-0.5-7.1-2.7-10.1c-1.7-2.9-5.4-2.7-8.3-1.9
|
||||
C255.8,263.8,253,260.7,253.6,257.7L253.6,257.7z"/>
|
||||
<path id="path1131" class="st0" d="M300.8,230c3.3-1.9,7.5,0.9,6.7,4.6c0,2.3-2.2,3.6-3.5,5.2c-1.9,1.9-2.3,4.9-1.2,7
|
||||
c1.3,2.9,4.9,4,5.5,7.2c1.2,4.8-3.3,10.1-8.2,9.8c-4.8,0.1-8.3-5-6.3-9.5c1.2-3.8,5.8-4.9,7.2-8.5c1.7-3.2-0.4-6.1-2.4-8.1
|
||||
C296.7,235.6,297.9,231.4,300.8,230z"/>
|
||||
</g>
|
||||
<g id="g1149" transform="translate(-244.51235,-228.78793)">
|
||||
<path id="path1135" class="st0" d="M256,311.5c-2.6,1-5.5-0.4-6.2-3c-0.7-2.6,1.1-5.7,3.7-6.4c3.2-0.6,6.2,1.4,9.3-0.2
|
||||
c3.1-1.6,4-5.1,4.7-8.2c1.4-5,8.5-7.3,12.1-4.2c3.3,2.4,3.4,7.8,0.2,10.9c-2.2,2.5-5.9,3.3-8.8,2c-2.7-1.2-6-1.7-8.6,0.4
|
||||
C259.1,305,259.9,310.2,256,311.5L256,311.5z"/>
|
||||
<path id="path1137" class="st0" d="M316.1,320.5c-4.2,2.2-9.5-1.5-8.2-6c1.4-5.4,0.7-11.6-2.9-15.6c-3.4-4-9.8-4.7-14.6-1.9
|
||||
c-3.7,2-6,5.7-7.4,9.5c-1,3.1-0.6,6.2-1.1,9.3c-1,3.8-5.8,6-9.1,4.3c-3.2-1.4-4-5.9-1.7-8.9c1.4-2.2,4.2-2.7,6.3-3.8
|
||||
c3.6-1.9,6.5-4.9,8.4-8.6c1.2-2,1.1-4.5,1.7-6.6c0.9-1.9,3-2.7,4.8-2.9c4.8-0.6,9.5-3,13-6.7c1.7-1.6,3.1-4.2,5.6-4.1
|
||||
c2.7,0.1,4.7,2.5,7.4,2.7c4.9,0.6,10.2-0.8,14.4-4c3.7-3.2,9.4-0.9,9.6,3.6c0.9,5.1-6.2,9.5-10.1,6.2c-4.3-4-10.8-5.6-16.9-3.7
|
||||
c-5.6,1.9-9.7,8.3-8.6,14c0.9,4.8,4.2,9.3,8.6,11.3c1.7,0.8,3.6,1.6,4.2,3.5C321.2,314.9,319.4,319.1,316.1,320.5L316.1,320.5z"/>
|
||||
<path id="path1139" class="st0" d="M318.9,329.4c-2.2,0.8-4.2-1.3-3.3-3.4c0.6-2.1,3.7-2.7,4.8-1.1
|
||||
C321.7,326.3,320.7,328.8,318.9,329.4z"/>
|
||||
<path id="path1141" class="st0" d="M349.9,286.5c-2.2,0.7-4.2-1.3-3.3-3.5c0.6-2.1,3.7-2.7,4.8-1
|
||||
C352.8,283.5,351.8,285.9,349.9,286.5z"/>
|
||||
<path id="path1143" class="st0" d="M268.5,329c-2.2,0.7-4.2-1.3-3.3-3.5c0.6-2,3.7-2.7,4.8-1C271.3,325.9,270.3,328.5,268.5,329z"
|
||||
/>
|
||||
<path id="path1145" class="st0" d="M343.1,307.4c-0.4,3.7-5.5,5.9-8.1,3.6c-1.9-1.1-1.6-3.6-2.2-5.4c-0.4-2.4-2.7-4.3-5.2-4.3
|
||||
c-3.2-0.3-6.4,2.3-9.5,1.2c-4.8-1.2-6.5-7.5-3.2-11.5c2.9-4.1,9.3-4.5,12-0.7c2.4,2.8,0.5,7,2.7,10c1.7,2.9,5.4,2.7,8.3,1.9
|
||||
C341,301.4,343.8,304.4,343.1,307.4L343.1,307.4z"/>
|
||||
<path id="path1147" class="st0" d="M295.8,335.2c-3.3,2-7.5-0.9-6.7-4.6c0-2.3,2.2-3.6,3.5-5.2c1.9-1.9,2.3-4.9,1.2-7
|
||||
c-1.3-2.9-4.9-4-5.5-7.2c-1.2-4.8,3.3-10.1,8.2-9.8c4.8-0.1,8.3,5,6.3,9.6c-1.2,3.7-5.8,4.8-7.2,8.4c-1.7,3.2,0.4,6.1,2.4,8.2
|
||||
C300,329.6,298.8,333.8,295.8,335.2L295.8,335.2z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
BIN
docs/static/img/databases/apache-phoenix.png
vendored
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
docs/static/img/databases/neon.png
vendored
Normal file
|
After Width: | Height: | Size: 20 KiB |
1
docs/static/img/databases/supabase.svg
vendored
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
docs/static/img/logos/xnet.png
vendored
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
docs/static/img/superset-og-image.png
vendored
Normal file
|
After Width: | Height: | Size: 88 KiB |
4445
docs/yarn.lock
@@ -141,7 +141,7 @@ druid = ["pydruid>=0.6.5,<0.7"]
|
||||
duckdb = ["duckdb>=1.4.2,<2", "duckdb-engine>=0.17.0"]
|
||||
dynamodb = ["pydynamodb>=0.4.2"]
|
||||
solr = ["sqlalchemy-solr >= 0.2.0"]
|
||||
elasticsearch = ["elasticsearch-dbapi>=0.2.9, <0.3.0"]
|
||||
elasticsearch = ["elasticsearch-dbapi>=0.2.12, <0.3.0"]
|
||||
exasol = ["sqlalchemy-exasol >= 2.4.0, <3.0"]
|
||||
excel = ["xlrd>=1.2.0, <1.3"]
|
||||
fastmcp = ["fastmcp==2.14.3"]
|
||||
@@ -174,7 +174,7 @@ oracle = ["cx-Oracle>8.0.0, <8.1"]
|
||||
parseable = ["sqlalchemy-parseable>=0.1.3,<0.2.0"]
|
||||
pinot = ["pinotdb>=5.0.0, <6.0.0"]
|
||||
playwright = ["playwright>=1.37.0, <2"]
|
||||
postgres = ["psycopg2-binary==2.9.6"]
|
||||
postgres = ["psycopg2-binary==2.9.9"]
|
||||
presto = ["pyhive[presto]>=0.6.5"]
|
||||
trino = ["trino>=0.328.0"]
|
||||
prophet = ["prophet>=1.1.6, <2"]
|
||||
@@ -204,6 +204,7 @@ ydb = ["ydb-sqlalchemy>=0.1.2"]
|
||||
development = [
|
||||
# no bounds for apache-superset-extensions-cli until a stable version
|
||||
"apache-superset-extensions-cli",
|
||||
"boto3",
|
||||
"docker",
|
||||
"flask-testing",
|
||||
"freezegun",
|
||||
@@ -437,6 +438,7 @@ authorized_licenses = [
|
||||
"apache software",
|
||||
"apache software, bsd",
|
||||
"bsd",
|
||||
"bsd-2-clause",
|
||||
"bsd-3-clause",
|
||||
"isc license (iscl)",
|
||||
"isc license",
|
||||
|
||||
@@ -16,8 +16,14 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
urllib3>=2.6.0,<3.0.0
|
||||
werkzeug>=3.0.1
|
||||
# Security: CVE-2026-21441 - decompression bomb bypass on redirects
|
||||
urllib3>=2.6.3,<3.0.0
|
||||
# Security: GHSA-87hc-h4r5-73f7 - Windows path traversal fix
|
||||
werkzeug>=3.1.5,<4.0.0
|
||||
# Security: CVE-2025-68146 - TOCTOU symlink vulnerability
|
||||
filelock>=3.20.3,<4.0.0
|
||||
# Security: decompression bomb fix (required by aiohttp 3.13.3)
|
||||
brotli>=1.2.0,<2.0.0
|
||||
numexpr>=2.9.0
|
||||
|
||||
# 5.0.0 has a sensitive deprecation used in other libs
|
||||
|
||||
@@ -36,8 +36,10 @@ blinker==1.9.0
|
||||
# via flask
|
||||
bottleneck==1.5.0
|
||||
# via apache-superset (pyproject.toml)
|
||||
brotli==1.1.0
|
||||
# via flask-compress
|
||||
brotli==1.2.0
|
||||
# via
|
||||
# -r requirements/base.in
|
||||
# flask-compress
|
||||
cachelib==0.13.0
|
||||
# via
|
||||
# flask-caching
|
||||
@@ -101,6 +103,8 @@ email-validator==2.2.0
|
||||
# via flask-appbuilder
|
||||
et-xmlfile==2.0.0
|
||||
# via openpyxl
|
||||
filelock==3.20.3
|
||||
# via -r requirements/base.in
|
||||
flask==2.3.3
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
@@ -289,7 +293,7 @@ prompt-toolkit==3.0.51
|
||||
# via click-repl
|
||||
pyarrow==16.1.0
|
||||
# via apache-superset (pyproject.toml)
|
||||
pyasn1==0.6.1
|
||||
pyasn1==0.6.2
|
||||
# via
|
||||
# pyasn1-modules
|
||||
# rsa
|
||||
@@ -436,7 +440,7 @@ tzdata==2025.2
|
||||
# pandas
|
||||
url-normalize==2.2.1
|
||||
# via requests-cache
|
||||
urllib3==2.6.0
|
||||
urllib3==2.6.3
|
||||
# via
|
||||
# -r requirements/base.in
|
||||
# requests
|
||||
@@ -453,7 +457,7 @@ wcwidth==0.2.13
|
||||
# via prompt-toolkit
|
||||
websocket-client==1.8.0
|
||||
# via selenium
|
||||
werkzeug==3.1.3
|
||||
werkzeug==3.1.5
|
||||
# via
|
||||
# -r requirements/base.in
|
||||
# flask
|
||||
|
||||
@@ -76,11 +76,17 @@ blinker==1.9.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask
|
||||
boto3==1.42.39
|
||||
# via apache-superset
|
||||
botocore==1.42.39
|
||||
# via
|
||||
# boto3
|
||||
# s3transfer
|
||||
bottleneck==1.5.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
brotli==1.1.0
|
||||
brotli==1.2.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask-compress
|
||||
@@ -235,8 +241,10 @@ fakeredis==2.32.1
|
||||
# via pydocket
|
||||
fastmcp==2.14.3
|
||||
# via apache-superset
|
||||
filelock==3.12.2
|
||||
# via virtualenv
|
||||
filelock==3.20.3
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# virtualenv
|
||||
flask==2.3.3
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -458,6 +466,10 @@ jinja2==3.1.6
|
||||
# apache-superset-extensions-cli
|
||||
# flask
|
||||
# flask-babel
|
||||
jmespath==1.1.0
|
||||
# via
|
||||
# boto3
|
||||
# botocore
|
||||
jsonpath-ng==1.7.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -700,7 +712,7 @@ protobuf==4.25.5
|
||||
# proto-plus
|
||||
psutil==6.1.0
|
||||
# via apache-superset
|
||||
psycopg2-binary==2.9.6
|
||||
psycopg2-binary==2.9.9
|
||||
# via apache-superset
|
||||
py-key-value-aio==0.3.0
|
||||
# via
|
||||
@@ -714,7 +726,7 @@ pyarrow==16.1.0
|
||||
# apache-superset
|
||||
# db-dtypes
|
||||
# pandas-gbq
|
||||
pyasn1==0.6.1
|
||||
pyasn1==0.6.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# pyasn1-modules
|
||||
@@ -810,6 +822,7 @@ python-dateutil==2.9.0.post0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
# botocore
|
||||
# celery
|
||||
# croniter
|
||||
# flask-appbuilder
|
||||
@@ -913,6 +926,8 @@ rsa==4.9.1
|
||||
# google-auth
|
||||
ruff==0.9.7
|
||||
# via apache-superset
|
||||
s3transfer==0.16.0
|
||||
# via boto3
|
||||
secretstorage==3.5.0
|
||||
# via keyring
|
||||
selenium==4.32.0
|
||||
@@ -1061,9 +1076,10 @@ url-normalize==2.2.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# requests-cache
|
||||
urllib3==2.6.0
|
||||
urllib3==2.6.3
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# botocore
|
||||
# docker
|
||||
# requests
|
||||
# requests-cache
|
||||
@@ -1095,7 +1111,7 @@ websocket-client==1.8.0
|
||||
# selenium
|
||||
websockets==15.0.1
|
||||
# via fastmcp
|
||||
werkzeug==3.1.3
|
||||
werkzeug==3.1.5
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask
|
||||
|
||||
1114
superset-embedded-sdk/package-lock.json
generated
@@ -273,6 +273,53 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
// Ban JavaScript files in src/ - all new code must be TypeScript
|
||||
{
|
||||
files: ['src/**/*.js', 'src/**/*.jsx'],
|
||||
rules: {
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector: 'Program',
|
||||
message:
|
||||
'JavaScript files are not allowed in src/. Please use TypeScript (.ts/.tsx) instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// Ban JavaScript files in plugins/ - all plugin source code must be TypeScript
|
||||
{
|
||||
files: ['plugins/**/src/**/*.js', 'plugins/**/src/**/*.jsx'],
|
||||
rules: {
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector: 'Program',
|
||||
message:
|
||||
'JavaScript files are not allowed in plugins/. Please use TypeScript (.ts/.tsx) instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// Ban JavaScript files in packages/ - with exceptions for config files and generators
|
||||
{
|
||||
files: ['packages/**/src/**/*.js', 'packages/**/src/**/*.jsx'],
|
||||
excludedFiles: [
|
||||
'packages/generator-superset/**/*', // Yeoman generator templates run via Node
|
||||
'packages/superset-ui-demo/.storybook/**/*', // Storybook config files
|
||||
'packages/**/__mocks__/**/*', // Test mocks
|
||||
],
|
||||
rules: {
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector: 'Program',
|
||||
message:
|
||||
'JavaScript files are not allowed in packages/. Please use TypeScript (.ts/.tsx) instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"decorators": false,
|
||||
"dynamicImport": true
|
||||
},
|
||||
"transform": {
|
||||
"react": {
|
||||
"runtime": "automatic",
|
||||
"importSource": "@emotion/react",
|
||||
"throwIfNamespace": true
|
||||
},
|
||||
"optimizer": {
|
||||
"globals": {
|
||||
"vars": {
|
||||
"process.env.NODE_ENV": "production"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"target": "es2015",
|
||||
"loose": true,
|
||||
"externalHelpers": false,
|
||||
"preserveAllComments": false,
|
||||
"experimental": {
|
||||
"plugins": [
|
||||
[
|
||||
"@swc/plugin-emotion",
|
||||
{
|
||||
"sourceMap": true,
|
||||
"autoLabel": "dev-only",
|
||||
"labelFormat": "[local]"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@swc/plugin-transform-imports",
|
||||
{
|
||||
"lodash": {
|
||||
"transform": "lodash/{{member}}",
|
||||
"preventFullImport": true,
|
||||
"skipDefaultConversion": false
|
||||
},
|
||||
"lodash-es": {
|
||||
"transform": "lodash-es/{{member}}",
|
||||
"preventFullImport": true,
|
||||
"skipDefaultConversion": false
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "es6",
|
||||
"strict": false,
|
||||
"strictMode": false,
|
||||
"lazy": false,
|
||||
"noInterop": false
|
||||
},
|
||||
"minify": false
|
||||
}
|
||||
@@ -52,9 +52,6 @@ module.exports = {
|
||||
['@babel/plugin-transform-private-methods', { loose: true }],
|
||||
['@babel/plugin-transform-nullish-coalescing-operator', { loose: true }],
|
||||
['@babel/plugin-transform-runtime', { corejs: 3 }],
|
||||
// only used in packages/superset-ui-core/src/chart/components/reactify.tsx
|
||||
['babel-plugin-typescript-to-proptypes', { loose: true }],
|
||||
'react-hot-loader/babel',
|
||||
[
|
||||
'@emotion/babel-plugin',
|
||||
{
|
||||
|
||||
@@ -22,27 +22,40 @@
|
||||
* @author Apache
|
||||
*/
|
||||
|
||||
import type { Rule } from 'eslint';
|
||||
import type { Node } from 'estree';
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
module.exports = {
|
||||
const plugin: { rules: Record<string, Rule.RuleModule> } = {
|
||||
rules: {
|
||||
'no-template-vars': {
|
||||
create(context) {
|
||||
function handler(node) {
|
||||
if (node.arguments.length) {
|
||||
const firstArgs = node.arguments[0];
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: 'Disallow variables in translation template strings',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context: Rule.RuleContext): Rule.RuleListener {
|
||||
function handler(node: Node): void {
|
||||
const callNode = node as Node & {
|
||||
arguments: Array<Node & { type: string; expressions?: Node[] }>;
|
||||
};
|
||||
// Check all arguments (e.g., tn has singular and plural templates)
|
||||
for (const arg of callNode.arguments ?? []) {
|
||||
if (
|
||||
firstArgs.type === 'TemplateLiteral' &&
|
||||
firstArgs.expressions.length
|
||||
arg.type === 'TemplateLiteral' &&
|
||||
(arg as Node & { expressions?: Node[] }).expressions?.length
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
message:
|
||||
"Don't use variables in translation string templates. Flask-babel is a static translation service, so it can't handle strings that include variables",
|
||||
});
|
||||
break; // Only report once per call
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,19 +66,29 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
'sentence-case-buttons': {
|
||||
create(context) {
|
||||
function isTitleCase(str) {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Enforce sentence case for button text in translations',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context: Rule.RuleContext): Rule.RuleListener {
|
||||
function isTitleCase(str: string): boolean {
|
||||
// Match "Delete Dataset", "Create Chart", etc. (2+ title-cased words)
|
||||
return /^[A-Z][a-z]+(\s+[A-Z][a-z]*)+$/.test(str);
|
||||
}
|
||||
|
||||
function isButtonContext(node) {
|
||||
const { parent } = node;
|
||||
function isButtonContext(node: Node & { parent?: Node }): boolean {
|
||||
const { parent } = node as Node & {
|
||||
parent?: Node & Record<string, unknown>;
|
||||
};
|
||||
if (!parent) return false;
|
||||
|
||||
// Check for button-specific props
|
||||
if (parent.type === 'Property') {
|
||||
const key = parent.key.name;
|
||||
const key = (parent as unknown as { key: { name: string } }).key
|
||||
.name;
|
||||
return [
|
||||
'primaryButtonName',
|
||||
'secondaryButtonName',
|
||||
@@ -75,10 +98,16 @@ module.exports = {
|
||||
}
|
||||
|
||||
// Check for Button components
|
||||
if (parent.type === 'JSXExpressionContainer') {
|
||||
const jsx = parent.parent;
|
||||
if (jsx?.type === 'JSXElement') {
|
||||
const elementName = jsx.openingElement.name.name;
|
||||
// Cast to string because ESTree Node type doesn't include JSX types
|
||||
if ((parent.type as string) === 'JSXExpressionContainer') {
|
||||
const jsx = (parent as Node & { parent?: Node }).parent as
|
||||
| (Node & {
|
||||
type: string;
|
||||
openingElement?: { name: { name: string } };
|
||||
})
|
||||
| undefined;
|
||||
if ((jsx?.type as string) === 'JSXElement') {
|
||||
const elementName = jsx?.openingElement?.name.name;
|
||||
return elementName === 'Button';
|
||||
}
|
||||
}
|
||||
@@ -86,21 +115,24 @@ module.exports = {
|
||||
return false;
|
||||
}
|
||||
|
||||
function handler(node) {
|
||||
if (node.arguments.length) {
|
||||
const firstArg = node.arguments[0];
|
||||
if (
|
||||
firstArg.type === 'Literal' &&
|
||||
typeof firstArg.value === 'string'
|
||||
) {
|
||||
const text = firstArg.value;
|
||||
function handler(node: Node): void {
|
||||
const callNode = node as Node & {
|
||||
arguments: Array<Node & { type: string; value?: unknown }>;
|
||||
};
|
||||
// Check all string literal arguments (e.g., tn has singular and plural)
|
||||
for (const arg of callNode.arguments ?? []) {
|
||||
if (arg.type === 'Literal' && typeof arg.value === 'string') {
|
||||
const text = arg.value;
|
||||
|
||||
if (isButtonContext(node) && isTitleCase(text)) {
|
||||
if (
|
||||
isButtonContext(node as Node & { parent?: Node }) &&
|
||||
isTitleCase(text)
|
||||
) {
|
||||
const sentenceCase = text
|
||||
.toLowerCase()
|
||||
.replace(/^\w/, c => c.toUpperCase());
|
||||
.replace(/^\w/, (c: string) => c.toUpperCase());
|
||||
context.report({
|
||||
node: firstArg,
|
||||
node: arg,
|
||||
message: `Button text should use sentence case: "${text}" should be "${sentenceCase}"`,
|
||||
});
|
||||
}
|
||||
@@ -116,3 +148,5 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = plugin;
|
||||
@@ -22,17 +22,19 @@
|
||||
* @author Apache
|
||||
*/
|
||||
/* eslint-disable no-template-curly-in-string */
|
||||
import type { Rule } from 'eslint';
|
||||
|
||||
const { RuleTester } = require('eslint');
|
||||
const plugin = require('.');
|
||||
const plugin: { rules: Record<string, Rule.RuleModule> } = require('.');
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Tests
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
|
||||
const rule = plugin.rules['no-template-vars'];
|
||||
const rule: Rule.RuleModule = plugin.rules['no-template-vars'];
|
||||
|
||||
const errors = [
|
||||
const errors: Array<{ type: string }> = [
|
||||
{
|
||||
type: 'CallExpression',
|
||||
},
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "eslint-plugin-i18n-strings",
|
||||
"version": "1.0.0",
|
||||
"description": "Warns about translation variables",
|
||||
"main": "index.js",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
|
||||
@@ -22,12 +22,29 @@
|
||||
* @author Apache
|
||||
*/
|
||||
|
||||
import type { Rule } from 'eslint';
|
||||
import type { Node } from 'estree';
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
module.exports = {
|
||||
interface JSXAttribute {
|
||||
name?: { name: string };
|
||||
value?: { type: string; value?: string; expression?: { value: string } };
|
||||
}
|
||||
|
||||
interface JSXOpeningElement {
|
||||
name: { name: string };
|
||||
attributes: JSXAttribute[];
|
||||
}
|
||||
|
||||
interface JSXElementNode {
|
||||
type: string;
|
||||
openingElement: JSXOpeningElement;
|
||||
}
|
||||
|
||||
const plugin: { rules: Record<string, Rule.RuleModule> } = {
|
||||
rules: {
|
||||
'no-fa-icons-usage': {
|
||||
meta: {
|
||||
@@ -39,20 +56,27 @@ module.exports = {
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
create(context: Rule.RuleContext): Rule.RuleListener {
|
||||
return {
|
||||
// Check for JSX elements with class names containing "fa"
|
||||
JSXElement(node) {
|
||||
JSXElement(node: Node): void {
|
||||
const jsxNode = node as unknown as JSXElementNode;
|
||||
if (
|
||||
node.openingElement &&
|
||||
node.openingElement.name.name === 'i' &&
|
||||
node.openingElement.attributes &&
|
||||
node.openingElement.attributes.some(
|
||||
attr =>
|
||||
attr.name &&
|
||||
attr.name.name === 'className' &&
|
||||
/fa fa-/.test(attr.value.value),
|
||||
)
|
||||
jsxNode.openingElement &&
|
||||
jsxNode.openingElement.name.name === 'i' &&
|
||||
jsxNode.openingElement.attributes &&
|
||||
jsxNode.openingElement.attributes.some((attr: JSXAttribute) => {
|
||||
if (attr.name?.name !== 'className') return false;
|
||||
// Handle className="fa fa-home"
|
||||
if (attr.value?.type === 'Literal') {
|
||||
return /fa fa-/.test(attr.value.value ?? '');
|
||||
}
|
||||
// Handle className={'fa fa-home'}
|
||||
if (attr.value?.type === 'JSXExpressionContainer') {
|
||||
return /fa fa-/.test(attr.value.expression?.value ?? '');
|
||||
}
|
||||
return false;
|
||||
})
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
@@ -66,3 +90,5 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = plugin;
|
||||
@@ -22,16 +22,20 @@
|
||||
* @author Apache
|
||||
*/
|
||||
|
||||
import type { Rule } from 'eslint';
|
||||
|
||||
const { RuleTester } = require('eslint');
|
||||
const plugin = require('.');
|
||||
const plugin: { rules: Record<string, Rule.RuleModule> } = require('.');
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Tests
|
||||
//------------------------------------------------------------------------------
|
||||
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
|
||||
const rule = plugin.rules['no-fa-icons-usage'];
|
||||
const ruleTester = new RuleTester({
|
||||
parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } },
|
||||
});
|
||||
const rule: Rule.RuleModule = plugin.rules['no-fa-icons-usage'];
|
||||
|
||||
const errors = [
|
||||
const errors: Array<{ message: string }> = [
|
||||
{
|
||||
message:
|
||||
'FontAwesome icons should not be used. Use the src/components/Icons component instead.',
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "eslint-plugin-icons",
|
||||
"version": "1.0.0",
|
||||
"description": "Warns about direct usage of Ant Design icons",
|
||||
"main": "index.js",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
// https://www.w3.org/wiki/CSS/Properties/color/keywords
|
||||
module.exports = [
|
||||
const COLOR_KEYWORDS: string[] = [
|
||||
'black',
|
||||
'silver',
|
||||
'gray',
|
||||
@@ -170,3 +170,5 @@ module.exports = [
|
||||
'whitesmoke',
|
||||
'yellowgreen',
|
||||
];
|
||||
|
||||
export default COLOR_KEYWORDS;
|
||||
@@ -1,119 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Rule to warn about literal colors
|
||||
* @author Apache
|
||||
*/
|
||||
|
||||
const COLOR_KEYWORDS = require('./colors');
|
||||
|
||||
function hasHexColor(quasi) {
|
||||
if (typeof quasi === 'string') {
|
||||
const regex = /#([a-f0-9]{3}|[a-f0-9]{4}(?:[a-f0-9]{2}){0,2})\b/gi;
|
||||
return !!quasi.match(regex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasRgbColor(quasi) {
|
||||
if (typeof quasi === 'string') {
|
||||
const regex = /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/i;
|
||||
return !!quasi.match(regex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasLiteralColor(quasi, strict = false) {
|
||||
if (typeof quasi === 'string') {
|
||||
// matches literal colors at the start or end of a CSS prop
|
||||
return COLOR_KEYWORDS.some(color => {
|
||||
const regexColon = new RegExp(`: ${color}`);
|
||||
const regexSemicolon = new RegExp(` ${color};`);
|
||||
return (
|
||||
!!quasi.match(regexColon) ||
|
||||
!!quasi.match(regexSemicolon) ||
|
||||
(strict && quasi === color)
|
||||
);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const WARNING_MESSAGE =
|
||||
'Theme color variables are preferred over rgb(a)/hex/literal colors';
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
module.exports = {
|
||||
rules: {
|
||||
'no-literal-colors': {
|
||||
create(context) {
|
||||
const warned = [];
|
||||
return {
|
||||
TemplateElement(node) {
|
||||
const rawValue = node?.value?.raw;
|
||||
const isChildParentTagged =
|
||||
node?.parent?.parent?.type === 'TaggedTemplateExpression';
|
||||
const isChildParentArrow =
|
||||
node?.parent?.parent?.type === 'ArrowFunctionExpression';
|
||||
const isParentTemplateLiteral =
|
||||
node?.parent?.type === 'TemplateLiteral';
|
||||
const loc = node?.parent?.parent?.loc;
|
||||
const locId = loc && JSON.stringify(loc);
|
||||
const hasWarned = warned.includes(locId);
|
||||
if (
|
||||
!hasWarned &&
|
||||
(isChildParentTagged ||
|
||||
(isChildParentArrow && isParentTemplateLiteral)) &&
|
||||
rawValue &&
|
||||
(hasLiteralColor(rawValue) ||
|
||||
hasHexColor(rawValue) ||
|
||||
hasRgbColor(rawValue))
|
||||
) {
|
||||
context.report(node, loc, WARNING_MESSAGE);
|
||||
warned.push(locId);
|
||||
}
|
||||
},
|
||||
Literal(node) {
|
||||
const value = node?.value;
|
||||
const isParentProperty = node?.parent?.type === 'Property';
|
||||
const locId = JSON.stringify(node.loc);
|
||||
const hasWarned = warned.includes(locId);
|
||||
|
||||
if (
|
||||
!hasWarned &&
|
||||
isParentProperty &&
|
||||
value &&
|
||||
(hasLiteralColor(value, true) ||
|
||||
hasHexColor(value) ||
|
||||
hasRgbColor(value))
|
||||
) {
|
||||
context.report(node, node.loc, WARNING_MESSAGE);
|
||||
warned.push(locId);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Rule to warn about literal colors
|
||||
* @author Apache
|
||||
*/
|
||||
|
||||
import type { Rule } from 'eslint';
|
||||
import type { Node, SourceLocation } from 'estree';
|
||||
|
||||
import COLOR_KEYWORDS from './colors';
|
||||
|
||||
function hasHexColor(quasi: string): boolean {
|
||||
const regex = /#([a-f0-9]{3}|[a-f0-9]{4}(?:[a-f0-9]{2}){0,2})\b/gi;
|
||||
return !!quasi.match(regex);
|
||||
}
|
||||
|
||||
function hasRgbColor(quasi: string): boolean {
|
||||
const regex = /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/i;
|
||||
return !!quasi.match(regex);
|
||||
}
|
||||
|
||||
function hasLiteralColor(quasi: string, strict: boolean = false): boolean {
|
||||
// matches literal colors at the start or end of a CSS prop
|
||||
return COLOR_KEYWORDS.some((color: string) => {
|
||||
const regexColon = new RegExp(`: ${color}`);
|
||||
const regexSemicolon = new RegExp(` ${color};`);
|
||||
return (
|
||||
!!quasi.match(regexColon) ||
|
||||
!!quasi.match(regexSemicolon) ||
|
||||
(strict && quasi === color)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const WARNING_MESSAGE: string =
|
||||
'Theme color variables are preferred over rgb(a)/hex/literal colors';
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
interface TemplateElementNode {
|
||||
type: string;
|
||||
value?: { raw: string };
|
||||
loc?: SourceLocation | null;
|
||||
parent?: {
|
||||
type: string;
|
||||
parent?: { type: string; loc?: SourceLocation | null };
|
||||
};
|
||||
}
|
||||
|
||||
interface LiteralNode {
|
||||
type: string;
|
||||
value?: unknown;
|
||||
loc?: SourceLocation | null;
|
||||
parent?: { type: string };
|
||||
}
|
||||
|
||||
const plugin: { rules: Record<string, Rule.RuleModule> } = {
|
||||
rules: {
|
||||
'no-literal-colors': {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description:
|
||||
'Disallow literal color values; use theme colors instead',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context: Rule.RuleContext): Rule.RuleListener {
|
||||
const warned: string[] = [];
|
||||
return {
|
||||
TemplateElement(node: Node): void {
|
||||
const templateNode = node as TemplateElementNode;
|
||||
const rawValue = templateNode?.value?.raw;
|
||||
const isChildParentTagged =
|
||||
templateNode?.parent?.parent?.type === 'TaggedTemplateExpression';
|
||||
const isChildParentArrow =
|
||||
templateNode?.parent?.parent?.type === 'ArrowFunctionExpression';
|
||||
const isParentTemplateLiteral =
|
||||
templateNode?.parent?.type === 'TemplateLiteral';
|
||||
const loc = templateNode?.parent?.parent?.loc;
|
||||
const locId = loc && JSON.stringify(loc);
|
||||
const hasWarned = locId ? warned.includes(locId) : false;
|
||||
if (
|
||||
!hasWarned &&
|
||||
(isChildParentTagged ||
|
||||
(isChildParentArrow && isParentTemplateLiteral)) &&
|
||||
rawValue &&
|
||||
(hasLiteralColor(rawValue) ||
|
||||
hasHexColor(rawValue) ||
|
||||
hasRgbColor(rawValue))
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
...(loc && { loc: loc as SourceLocation }),
|
||||
message: WARNING_MESSAGE,
|
||||
});
|
||||
if (locId) {
|
||||
warned.push(locId);
|
||||
}
|
||||
}
|
||||
},
|
||||
Literal(node: Node): void {
|
||||
const literalNode = node as LiteralNode;
|
||||
const value = literalNode?.value;
|
||||
// Only process string literals (not numbers, booleans, null, or RegExp)
|
||||
if (typeof value !== 'string') {
|
||||
return;
|
||||
}
|
||||
const parent = literalNode?.parent as Node & {
|
||||
type: string;
|
||||
value?: Node;
|
||||
};
|
||||
// Only check property values, not keys (e.g., { color: 'red' } not { red: 1 })
|
||||
const isPropertyValue =
|
||||
parent?.type === 'Property' && parent.value === node;
|
||||
const locId = node.loc ? JSON.stringify(node.loc) : null;
|
||||
const hasWarned = locId ? warned.includes(locId) : false;
|
||||
|
||||
if (
|
||||
!hasWarned &&
|
||||
isPropertyValue &&
|
||||
(hasLiteralColor(value, true) ||
|
||||
hasHexColor(value) ||
|
||||
hasRgbColor(value))
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
...(node.loc && { loc: node.loc as SourceLocation }),
|
||||
message: WARNING_MESSAGE,
|
||||
});
|
||||
if (locId) {
|
||||
warned.push(locId);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = plugin;
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "eslint-plugin-theme-colors",
|
||||
"version": "1.0.0",
|
||||
"description": "Warns about rgb(a)/hex/literal colors",
|
||||
"main": "index.js",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
|
||||
@@ -36,7 +36,13 @@ module.exports = {
|
||||
'^@apache-superset/core/(.*)$': '<rootDir>/packages/superset-core/src/$1',
|
||||
},
|
||||
testEnvironment: '<rootDir>/spec/helpers/jsDomWithFetchAPI.ts',
|
||||
modulePathIgnorePatterns: ['<rootDir>/packages/generator-superset'],
|
||||
modulePathIgnorePatterns: [
|
||||
'<rootDir>/packages/generator-superset',
|
||||
'<rootDir>/packages/.*/esm',
|
||||
'<rootDir>/packages/.*/lib',
|
||||
'<rootDir>/plugins/.*/esm',
|
||||
'<rootDir>/plugins/.*/lib',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/spec/helpers/setup.ts'],
|
||||
snapshotSerializers: ['@emotion/jest/serializer'],
|
||||
testEnvironmentOptions: {
|
||||
@@ -59,7 +65,7 @@ module.exports = {
|
||||
],
|
||||
coverageReporters: ['lcov', 'json-summary', 'html', 'text'],
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!d3-(array|interpolate|color|time|scale|time-format)|internmap|@mapbox/tiny-sdf|remark-gfm|(?!@ngrx|(?!deck.gl)|d3-scale)|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|@rjsf/*.|sinon|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|geostyler|geostyler-.*|react-error-boundary|react-json-tree|react-base16-styling|lodash-es)',
|
||||
'node_modules/(?!d3-(array|interpolate|color|time|scale|time-format|format)|internmap|@mapbox/tiny-sdf|remark-gfm|(?!@ngrx|(?!deck.gl)|d3-scale)|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|uuid|@rjsf/*.|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|geostyler|geostyler-.*|react-error-boundary|react-json-tree|react-base16-styling|lodash-es)',
|
||||
],
|
||||
preset: 'ts-jest',
|
||||
transform: {
|
||||
|
||||
1772
superset-frontend/package-lock.json
generated
@@ -97,6 +97,13 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@apache-superset/core": "file:packages/superset-core",
|
||||
"@deck.gl/aggregation-layers": "~9.2.5",
|
||||
"@deck.gl/core": "~9.2.5",
|
||||
"@deck.gl/extensions": "~9.2.5",
|
||||
"@deck.gl/geo-layers": "~9.2.5",
|
||||
"@deck.gl/layers": "~9.2.5",
|
||||
"@deck.gl/mesh-layers": "~9.2.5",
|
||||
"@deck.gl/react": "~9.2.5",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@@ -131,6 +138,7 @@
|
||||
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
|
||||
"@superset-ui/switchboard": "file:./packages/superset-ui-switchboard",
|
||||
"@types/d3-format": "^3.0.1",
|
||||
"@types/d3-selection": "^3.0.11",
|
||||
"@types/d3-time-format": "^4.0.3",
|
||||
"@types/react-google-recaptcha": "^2.1.9",
|
||||
"@visx/axis": "^3.8.0",
|
||||
@@ -161,7 +169,7 @@
|
||||
"geostyler-openlayers-parser": "^4.3.0",
|
||||
"geostyler-style": "7.5.0",
|
||||
"geostyler-wfs-parser": "^2.0.3",
|
||||
"googleapis": "^170.1.0",
|
||||
"googleapis": "^171.4.0",
|
||||
"immer": "^11.1.3",
|
||||
"interweave": "^13.1.1",
|
||||
"jquery": "^4.0.0",
|
||||
@@ -170,31 +178,36 @@
|
||||
"json-bigint": "^1.0.0",
|
||||
"json-stringify-pretty-compact": "^2.0.0",
|
||||
"lodash": "^4.17.23",
|
||||
"@luma.gl/constants": "~9.2.5",
|
||||
"@luma.gl/core": "~9.2.5",
|
||||
"@luma.gl/engine": "~9.2.5",
|
||||
"@luma.gl/gltf": "~9.2.5",
|
||||
"@luma.gl/shadertools": "~9.2.5",
|
||||
"@luma.gl/webgl": "~9.2.5",
|
||||
"mapbox-gl": "^3.18.1",
|
||||
"markdown-to-jsx": "^9.6.1",
|
||||
"markdown-to-jsx": "^9.7.3",
|
||||
"match-sorter": "^6.3.4",
|
||||
"memoize-one": "^5.2.1",
|
||||
"mousetrap": "^1.6.5",
|
||||
"mustache": "^4.2.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"ol": "^7.5.2",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "6.14.1",
|
||||
"query-string": "9.3.1",
|
||||
"re-resizable": "^6.11.2",
|
||||
"react": "^17.0.2",
|
||||
"react-arborist": "^3.4.3",
|
||||
"react-checkbox-tree": "^1.8.0",
|
||||
"react-diff-viewer-continued": "^3.4.0",
|
||||
"react-dnd": "^11.1.3",
|
||||
"react-dnd-html5-backend": "^11.1.3",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-google-recaptcha": "^3.1.0",
|
||||
"react-hot-loader": "^4.13.1",
|
||||
"react-intersection-observer": "^10.0.2",
|
||||
"react-json-tree": "^0.20.0",
|
||||
"react-lines-ellipsis": "^0.16.1",
|
||||
"react-loadable": "^5.5.0",
|
||||
"react-redux": "^7.2.9",
|
||||
"react-resize-detector": "^7.1.2",
|
||||
"react-resize-detector": "^9.1.1",
|
||||
"react-reverse-portal": "^2.3.0",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"react-search-input": "^0.11.3",
|
||||
@@ -216,46 +229,46 @@
|
||||
"urijs": "^1.19.8",
|
||||
"use-event-callback": "^0.1.0",
|
||||
"use-immer": "^0.11.0",
|
||||
"use-query-params": "^1.1.9",
|
||||
"use-query-params": "^2.2.2",
|
||||
"uuid": "^13.0.0",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@applitools/eyes-storybook": "^3.63.9",
|
||||
"@applitools/eyes-storybook": "^3.63.10",
|
||||
"@babel/cli": "^7.28.6",
|
||||
"@babel/compat-data": "^7.28.4",
|
||||
"@babel/core": "^7.28.6",
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/eslint-parser": "^7.28.6",
|
||||
"@babel/node": "^7.28.6",
|
||||
"@babel/node": "^7.29.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
|
||||
"@babel/plugin-transform-runtime": "^7.28.5",
|
||||
"@babel/preset-env": "^7.28.6",
|
||||
"@babel/plugin-transform-runtime": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.0",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@babel/preset-typescript": "^7.28.5",
|
||||
"@babel/register": "^7.23.7",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"@babel/runtime-corejs3": "^7.28.6",
|
||||
"@babel/runtime-corejs3": "^7.29.0",
|
||||
"@babel/types": "^7.28.6",
|
||||
"@cypress/react": "^8.0.2",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/jest": "^11.14.2",
|
||||
"@hot-loader/react-dom": "^17.0.2",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@mihkeleidast/storybook-addon-source": "^1.0.1",
|
||||
"@playwright/test": "^1.58.0",
|
||||
"@playwright/test": "^1.58.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
|
||||
"@storybook/addon-actions": "8.6.14",
|
||||
"@storybook/addon-controls": "8.6.14",
|
||||
"@storybook/addon-essentials": "8.6.14",
|
||||
"@storybook/addon-links": "8.6.14",
|
||||
"@storybook/addon-mdx-gfm": "8.6.14",
|
||||
"@storybook/components": "8.6.14",
|
||||
"@storybook/preview-api": "8.6.14",
|
||||
"@storybook/react": "8.6.14",
|
||||
"@storybook/react-webpack5": "8.6.14",
|
||||
"@storybook/test": "^8.6.14",
|
||||
"@storybook/addon-actions": "^8.6.15",
|
||||
"@storybook/addon-controls": "^8.6.15",
|
||||
"@storybook/addon-essentials": "^8.6.15",
|
||||
"@storybook/addon-links": "^8.6.15",
|
||||
"@storybook/addon-mdx-gfm": "^8.6.15",
|
||||
"@storybook/components": "^8.6.15",
|
||||
"@storybook/preview-api": "^8.6.15",
|
||||
"@storybook/react": "^8.6.15",
|
||||
"@storybook/react-webpack5": "^8.6.15",
|
||||
"@storybook/test": "^8.6.15",
|
||||
"@storybook/test-runner": "^0.17.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@swc/core": "^1.14.0",
|
||||
@@ -272,7 +285,7 @@
|
||||
"@types/js-levenshtein": "^1.1.3",
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^25.0.10",
|
||||
"@types/node": "^25.2.1",
|
||||
"@types/react": "^17.0.83",
|
||||
"@types/react-dom": "^17.0.26",
|
||||
"@types/react-loadable": "^5.5.11",
|
||||
@@ -284,8 +297,8 @@
|
||||
"@types/redux-localstorage": "^1.0.8",
|
||||
"@types/redux-mock-store": "^1.0.6",
|
||||
"@types/rison": "0.1.0",
|
||||
"@types/sinon": "^17.0.3",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
"@types/unzipper": "^0.10.11",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"babel-jest": "^30.0.2",
|
||||
@@ -293,13 +306,12 @@
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"babel-plugin-typescript-to-proptypes": "^2.0.0",
|
||||
"baseline-browser-mapping": "^2.9.18",
|
||||
"baseline-browser-mapping": "^2.9.19",
|
||||
"cheerio": "1.2.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"copy-webpack-plugin": "^13.0.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-loader": "^7.1.3",
|
||||
"css-minimizer-webpack-plugin": "^7.0.4",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^7.2.0",
|
||||
@@ -322,7 +334,7 @@
|
||||
"eslint-plugin-storybook": "^0.8.0",
|
||||
"eslint-plugin-testing-library": "^7.15.4",
|
||||
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
|
||||
"fetch-mock": "^11.1.5",
|
||||
"fetch-mock": "^12.6.0",
|
||||
"fork-ts-checker-webpack-plugin": "^9.1.0",
|
||||
"history": "^5.3.0",
|
||||
"html-webpack-plugin": "^5.6.6",
|
||||
@@ -332,7 +344,7 @@
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-html-reporter": "^4.3.0",
|
||||
"jest-websocket-mock": "^2.5.0",
|
||||
"jsdom": "^27.4.0",
|
||||
"jsdom": "^28.0.0",
|
||||
"lerna": "^8.2.3",
|
||||
"lightningcss": "^1.31.1",
|
||||
"mini-css-extract-plugin": "^2.10.0",
|
||||
@@ -345,7 +357,6 @@
|
||||
"react-refresh": "^0.18.0",
|
||||
"react-resizable": "^3.1.3",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
"sinon": "^18.0.0",
|
||||
"source-map": "^0.7.6",
|
||||
"source-map-support": "^0.5.21",
|
||||
"speed-measure-webpack-plugin": "^1.5.0",
|
||||
@@ -355,13 +366,13 @@
|
||||
"terser-webpack-plugin": "^5.3.16",
|
||||
"thread-loader": "^4.0.4",
|
||||
"ts-jest": "^29.4.6",
|
||||
"ts-loader": "^9.5.4",
|
||||
"tscw-config": "^1.1.2",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "5.4.5",
|
||||
"unzipper": "^0.12.3",
|
||||
"vm-browserify": "^1.1.2",
|
||||
"wait-on": "^9.0.3",
|
||||
"webpack": "^5.104.1",
|
||||
"webpack": "^5.105.0",
|
||||
"webpack-bundle-analyzer": "^5.2.0",
|
||||
"webpack-cli": "^6.0.1",
|
||||
"webpack-dev-server": "^5.2.3",
|
||||
@@ -386,21 +397,22 @@
|
||||
"puppeteer": "^22.4.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"underscore": "^1.13.7",
|
||||
"jspdf": "^3.0.2",
|
||||
"jspdf": "^4.0.0",
|
||||
"nwsapi": "^2.2.13",
|
||||
"@deck.gl/aggregation-layers": "~9.2.2",
|
||||
"@deck.gl/core": "~9.2.2",
|
||||
"@deck.gl/extensions": "~9.2.2",
|
||||
"@deck.gl/geo-layers": "~9.2.2",
|
||||
"@deck.gl/layers": "~9.2.2",
|
||||
"@deck.gl/mesh-layers": "~9.2.2",
|
||||
"@deck.gl/react": "~9.2.2",
|
||||
"@deck.gl/widgets": "~9.2.2",
|
||||
"@luma.gl/constants": "~9.2.2",
|
||||
"@luma.gl/core": "~9.2.2",
|
||||
"@luma.gl/engine": "~9.2.2",
|
||||
"@luma.gl/shadertools": "~9.2.2",
|
||||
"@luma.gl/webgl": "~9.2.2"
|
||||
"@deck.gl/aggregation-layers": "~9.2.5",
|
||||
"@deck.gl/core": "~9.2.5",
|
||||
"@deck.gl/extensions": "~9.2.5",
|
||||
"@deck.gl/geo-layers": "~9.2.5",
|
||||
"@deck.gl/layers": "~9.2.5",
|
||||
"@deck.gl/mesh-layers": "~9.2.5",
|
||||
"@deck.gl/react": "~9.2.5",
|
||||
"@deck.gl/widgets": "~9.2.5",
|
||||
"@luma.gl/constants": "~9.2.5",
|
||||
"@luma.gl/core": "~9.2.5",
|
||||
"@luma.gl/engine": "~9.2.5",
|
||||
"@luma.gl/gltf": "~9.2.5",
|
||||
"@luma.gl/shadertools": "~9.2.5",
|
||||
"@luma.gl/webgl": "~9.2.5"
|
||||
},
|
||||
"readme": "ERROR: No README data found!",
|
||||
"scarfSettings": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// @ts-ignore -- yeoman-test type resolution differs between local and Docker environments
|
||||
import helpers, { result } from 'yeoman-test';
|
||||
import appModule from '../generators/app';
|
||||
|
||||
@@ -18,15 +18,17 @@
|
||||
*/
|
||||
|
||||
import { dirname, join } from 'path';
|
||||
import helpers, { result } from 'yeoman-test';
|
||||
// @ts-ignore -- yeoman-test type resolution differs between local and Docker environments
|
||||
import helpers from 'yeoman-test';
|
||||
// @ts-ignore -- fs-extra/esm has no type declarations
|
||||
import { copySync } from 'fs-extra/esm';
|
||||
import { fileURLToPath } from 'url';
|
||||
import pluginChartModule from '../generators/plugin-chart';
|
||||
|
||||
test('generator-superset:plugin-chart:creates files', async () => {
|
||||
await helpers
|
||||
const result = await helpers
|
||||
.run(pluginChartModule)
|
||||
.onTargetDirectory(dir => {
|
||||
.onTargetDirectory((dir: string) => {
|
||||
// `dir` is the path to the new temporary directory
|
||||
const generatorDirname = dirname(fileURLToPath(import.meta.url));
|
||||
copySync(
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@apache-superset/core",
|
||||
"version": "0.0.1-rc9",
|
||||
"version": "0.0.1-rc10",
|
||||
"description": "This package contains UI elements, APIs, and utility functions used by Superset.",
|
||||
"sideEffects": false,
|
||||
"main": "lib/index.js",
|
||||
@@ -12,8 +12,8 @@
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.28.6",
|
||||
"@babel/core": "^7.28.6",
|
||||
"@babel/preset-env": "^7.28.6",
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.0",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@babel/preset-typescript": "^7.28.5",
|
||||
"install": "^0.13.0",
|
||||
|
||||
@@ -18,12 +18,19 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Editors API for Superset extension editor contributions.
|
||||
* @fileoverview Editors API for Superset text editor integration.
|
||||
*
|
||||
* This module defines the interfaces and types for editor contributions to the
|
||||
* Superset platform. Extensions can register custom text editor implementations
|
||||
* (e.g., Monaco, CodeMirror) through the extension manifest, replacing the
|
||||
* default Ace editor for specific languages.
|
||||
* This module defines the interfaces and types for working with text editors
|
||||
* in Superset. It provides:
|
||||
*
|
||||
* - `EditorHandle`: Imperative API for programmatically controlling editors
|
||||
* (get/set content, cursor position, selections, annotations, completions)
|
||||
* - `EditorProps`: Props contract for editor React components
|
||||
* - `CompletionProvider`: Interface for registering custom autocomplete providers
|
||||
* - Registration functions for custom editor implementations
|
||||
*
|
||||
* The API is editor-agnostic, supporting Ace, Monaco, CodeMirror, or any
|
||||
* compliant implementation.
|
||||
*/
|
||||
|
||||
import { ForwardRefExoticComponent, RefAttributes } from 'react';
|
||||
@@ -36,69 +43,111 @@ export type { EditorContribution, EditorLanguage };
|
||||
|
||||
/**
|
||||
* Represents a position in the editor (line and column).
|
||||
* Both line and column are zero-based indices.
|
||||
*
|
||||
* @example
|
||||
* // Position at the start of line 5, column 10
|
||||
* const pos: Position = { line: 4, column: 9 };
|
||||
*/
|
||||
export interface Position {
|
||||
/** Zero-based line number */
|
||||
/** Zero-based line number (first line is 0) */
|
||||
line: number;
|
||||
/** Zero-based column number */
|
||||
/** Zero-based column number (first column is 0) */
|
||||
column: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a range in the editor with start and end positions.
|
||||
* Represents a contiguous range in the editor defined by start and end positions.
|
||||
* The range is inclusive of the start position and exclusive of the end position.
|
||||
*/
|
||||
export interface Range {
|
||||
/** Start position of the range */
|
||||
/** Start position of the range (inclusive) */
|
||||
start: Position;
|
||||
/** End position of the range */
|
||||
/** End position of the range (exclusive) */
|
||||
end: Position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a selection in the editor.
|
||||
* Represents a selection in the editor, extending Range with direction information.
|
||||
* A selection is a highlighted range of text that can be manipulated.
|
||||
*/
|
||||
export interface Selection extends Range {
|
||||
/** Direction of the selection */
|
||||
/**
|
||||
* Direction of the selection.
|
||||
* - 'ltr': Selection was made left-to-right (anchor at start, cursor at end)
|
||||
* - 'rtl': Selection was made right-to-left (anchor at end, cursor at start)
|
||||
*/
|
||||
direction?: 'ltr' | 'rtl';
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotation severity levels for editor markers.
|
||||
* Severity levels for editor annotations.
|
||||
* Determines the visual style and icon used to display the annotation.
|
||||
*/
|
||||
export type AnnotationSeverity = 'error' | 'warning' | 'info';
|
||||
|
||||
/**
|
||||
* Represents an annotation (marker/diagnostic) in the editor.
|
||||
* Represents a diagnostic annotation displayed in the editor.
|
||||
* Annotations are used to highlight issues like syntax errors, linting warnings,
|
||||
* or informational messages at specific locations in the code.
|
||||
*
|
||||
* @example
|
||||
* const annotation: EditorAnnotation = {
|
||||
* line: 5,
|
||||
* column: 10,
|
||||
* message: 'Unknown column "user_id"',
|
||||
* severity: 'error',
|
||||
* source: 'sql-validator',
|
||||
* };
|
||||
*/
|
||||
export interface EditorAnnotation {
|
||||
/** Zero-based line number */
|
||||
/** Zero-based line number where the annotation appears */
|
||||
line: number;
|
||||
/** Zero-based column number (optional) */
|
||||
/** Zero-based column number for precise positioning (optional) */
|
||||
column?: number;
|
||||
/** Annotation message to display */
|
||||
/** Human-readable message describing the issue or information */
|
||||
message: string;
|
||||
/** Severity level of the annotation */
|
||||
/** Severity determines visual styling (red for error, yellow for warning, blue for info) */
|
||||
severity: AnnotationSeverity;
|
||||
/** Optional source of the annotation (e.g., "linter", "typescript") */
|
||||
/** Identifies what produced this annotation (e.g., "linter", "sql-validator") */
|
||||
source?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a keyboard shortcut binding.
|
||||
* Defines a keyboard shortcut that triggers a custom action in the editor.
|
||||
* Hotkeys allow binding key combinations to functions that manipulate
|
||||
* the editor or perform other actions.
|
||||
*
|
||||
* @example
|
||||
* const runQueryHotkey: EditorHotkey = {
|
||||
* name: 'runQuery',
|
||||
* key: 'Ctrl+Enter',
|
||||
* description: 'Execute the current query',
|
||||
* exec: (handle) => {
|
||||
* const sql = handle.getValue();
|
||||
* executeQuery(sql);
|
||||
* },
|
||||
* };
|
||||
*/
|
||||
export interface EditorHotkey {
|
||||
/** Unique name for the hotkey command */
|
||||
/** Unique identifier for this hotkey command */
|
||||
name: string;
|
||||
/** Key binding string (e.g., "Ctrl+Enter", "Alt+Enter") */
|
||||
/**
|
||||
* Key combination string. Format varies by editor but typically uses:
|
||||
* - Modifiers: Ctrl, Alt, Shift, Meta (Cmd on Mac)
|
||||
* - Separator: + (e.g., "Ctrl+Enter", "Ctrl+Shift+F")
|
||||
*/
|
||||
key: string;
|
||||
/** Description of what the hotkey does */
|
||||
/** Human-readable description shown in keyboard shortcut help */
|
||||
description?: string;
|
||||
/** Function to execute when the hotkey is triggered */
|
||||
/** Callback invoked when the hotkey is pressed, receives the editor handle */
|
||||
exec: (handle: EditorHandle) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completion item kinds for autocompletion.
|
||||
* Categories for completion items, determining the icon displayed.
|
||||
* Includes standard programming concepts plus SQL-specific types
|
||||
* (table, column, schema, catalog, database).
|
||||
*/
|
||||
export type CompletionItemKind =
|
||||
| 'text'
|
||||
@@ -132,53 +181,87 @@ export type CompletionItemKind =
|
||||
| 'database';
|
||||
|
||||
/**
|
||||
* Represents a completion item for autocompletion.
|
||||
* Represents a single item in the autocompletion dropdown.
|
||||
* Completion items are suggestions shown to users as they type,
|
||||
* allowing quick insertion of code snippets, keywords, or identifiers.
|
||||
*
|
||||
* @example
|
||||
* const tableCompletion: CompletionItem = {
|
||||
* label: 'users',
|
||||
* insertText: 'users',
|
||||
* kind: 'table',
|
||||
* detail: 'public schema',
|
||||
* documentation: 'User accounts table with profile information',
|
||||
* };
|
||||
*/
|
||||
export interface CompletionItem {
|
||||
/** Display label for the completion item */
|
||||
/** Text displayed in the completion dropdown */
|
||||
label: string;
|
||||
/** Text to insert when the item is selected */
|
||||
/** Text inserted into the editor when this item is selected */
|
||||
insertText: string;
|
||||
/** Kind of completion item for icon display */
|
||||
/** Category of completion, determines the icon shown (e.g., table, column, function) */
|
||||
kind: CompletionItemKind;
|
||||
/** Optional documentation to show in the completion popup */
|
||||
/** Extended description shown in a details pane or tooltip */
|
||||
documentation?: string;
|
||||
/** Optional detail text to show alongside the label */
|
||||
/** Short additional info displayed next to the label (e.g., type, schema) */
|
||||
detail?: string;
|
||||
/** Sorting priority (higher numbers appear first) */
|
||||
/** String used for sorting; items are sorted lexicographically by this value */
|
||||
sortText?: string;
|
||||
/** Text used for filtering completions */
|
||||
/** String used for filtering; if omitted, label is used for matching user input */
|
||||
filterText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Context provided to completion providers.
|
||||
* Context information passed to completion providers when requesting suggestions.
|
||||
* Contains details about how completion was triggered and the current environment.
|
||||
*/
|
||||
export interface CompletionContext {
|
||||
/** Character that triggered the completion (if any) */
|
||||
/** The character that triggered automatic completion (e.g., '.', ' '), if applicable */
|
||||
triggerCharacter?: string;
|
||||
/** How the completion was triggered */
|
||||
/**
|
||||
* How the completion was triggered:
|
||||
* - 'invoke': User explicitly requested completion (e.g., Ctrl+Space)
|
||||
* - 'automatic': Triggered automatically by typing a trigger character
|
||||
*/
|
||||
triggerKind: 'invoke' | 'automatic';
|
||||
/** Language of the editor */
|
||||
/** The language mode of the editor (e.g., 'sql', 'json') */
|
||||
language: EditorLanguage;
|
||||
/** Generic metadata passed from the host (e.g., SQL Lab can pass database context) */
|
||||
/** Host-provided context (e.g., database ID, schema name for SQL completions) */
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider interface for dynamic completions.
|
||||
* Interface for providing dynamic autocompletion suggestions.
|
||||
* Providers are invoked when the user triggers completion, allowing
|
||||
* context-aware suggestions based on cursor position and editor content.
|
||||
*
|
||||
* @example
|
||||
* const tableCompletionProvider: CompletionProvider = {
|
||||
* id: 'sql-tables',
|
||||
* triggerCharacters: [' ', '.'],
|
||||
* provideCompletions: async (content, position, context) => {
|
||||
* const dbId = context.metadata?.databaseId;
|
||||
* const tables = await fetchTables(dbId);
|
||||
* return tables.map(t => ({
|
||||
* label: t.name,
|
||||
* insertText: t.name,
|
||||
* kind: 'table',
|
||||
* }));
|
||||
* },
|
||||
* };
|
||||
*/
|
||||
export interface CompletionProvider {
|
||||
/** Unique identifier for this provider */
|
||||
/** Unique identifier for this provider, used for debugging and deduplication */
|
||||
id: string;
|
||||
/** Trigger characters that invoke this provider (e.g., '.', ' ') */
|
||||
/** Characters that trigger this provider automatically when typed (e.g., '.', ' ') */
|
||||
triggerCharacters?: string[];
|
||||
/**
|
||||
* Provide completions at the given position.
|
||||
* @param content The editor content
|
||||
* @param position The cursor position
|
||||
* @param context Completion context with trigger info and metadata
|
||||
* @returns Array of completion items or a promise that resolves to them
|
||||
* Generate completion suggestions for the current cursor position.
|
||||
*
|
||||
* @param content Full text content of the editor
|
||||
* @param position Current cursor position where completion was triggered
|
||||
* @param context Additional context about the trigger and environment
|
||||
* @returns Array of completion items, or a Promise resolving to them for async providers
|
||||
*/
|
||||
provideCompletions(
|
||||
content: string,
|
||||
@@ -188,98 +271,186 @@ export interface CompletionProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* A keyword for editor autocomplete.
|
||||
* This is a generic format that editor implementations convert to their native format.
|
||||
* Represents a static keyword for basic autocomplete.
|
||||
* Keywords are simpler than CompletionItems and are used for static lists
|
||||
* of suggestions (e.g., SQL keywords, table names) that don't require
|
||||
* dynamic computation.
|
||||
*
|
||||
* Editor implementations convert these to their native completion format.
|
||||
*
|
||||
* @example
|
||||
* const sqlKeywords: EditorKeyword[] = [
|
||||
* { name: 'SELECT', meta: 'keyword', score: 100 },
|
||||
* { name: 'FROM', meta: 'keyword', score: 100 },
|
||||
* { name: 'users', value: 'users', meta: 'table', score: 50 },
|
||||
* ];
|
||||
*/
|
||||
export interface EditorKeyword {
|
||||
/** Display name of the keyword */
|
||||
/** Display name shown in the completion dropdown */
|
||||
name: string;
|
||||
/** Value to insert when selected (defaults to name if not provided) */
|
||||
/** Text to insert when selected; defaults to name if not provided */
|
||||
value?: string;
|
||||
/** Category/type of the keyword (e.g., "column", "table", "function") */
|
||||
/** Category label shown alongside the name (e.g., "column", "table", "function") */
|
||||
meta?: string;
|
||||
/** Optional score for sorting (higher = more relevant) */
|
||||
/** Sorting priority; higher scores appear first in the completion list */
|
||||
score?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props that all editor implementations must accept.
|
||||
* Props accepted by all editor component implementations.
|
||||
* This interface defines the contract between Superset and editor components,
|
||||
* ensuring consistent behavior regardless of the underlying editor library.
|
||||
*/
|
||||
export interface EditorProps {
|
||||
/** Instance identifier */
|
||||
/** Unique identifier for this editor instance */
|
||||
id: string;
|
||||
/** Controlled value */
|
||||
/** Current editor content (controlled component pattern) */
|
||||
value: string;
|
||||
/** Content change handler */
|
||||
/** Called when the editor content changes */
|
||||
onChange: (value: string) => void;
|
||||
/** Blur handler */
|
||||
/** Called when the editor loses focus, with the current value */
|
||||
onBlur?: (value: string) => void;
|
||||
/** Cursor position change handler */
|
||||
/** Called when the cursor position changes */
|
||||
onCursorPositionChange?: (pos: Position) => void;
|
||||
/** Selection change handler */
|
||||
/** Called when the selection(s) change */
|
||||
onSelectionChange?: (sel: Selection[]) => void;
|
||||
/** Language mode for syntax highlighting */
|
||||
/** Language mode for syntax highlighting and language features */
|
||||
language: EditorLanguage;
|
||||
/** Whether the editor is read-only */
|
||||
/** When true, prevents editing (view-only mode) */
|
||||
readOnly?: boolean;
|
||||
/** Tab size in spaces */
|
||||
/** Number of spaces per tab character */
|
||||
tabSize?: number;
|
||||
/** Whether to show line numbers */
|
||||
/** Whether to display line numbers in the gutter */
|
||||
lineNumbers?: boolean;
|
||||
/** Whether to enable word wrap */
|
||||
/** Whether long lines should wrap to the next visual line */
|
||||
wordWrap?: boolean;
|
||||
/** Linting/error annotations */
|
||||
/** Diagnostic annotations to display (errors, warnings, info) */
|
||||
annotations?: EditorAnnotation[];
|
||||
/** Keyboard shortcuts */
|
||||
/** Custom keyboard shortcuts */
|
||||
hotkeys?: EditorHotkey[];
|
||||
/** Static keywords for autocomplete */
|
||||
/** Static keywords for basic autocomplete */
|
||||
keywords?: EditorKeyword[];
|
||||
/** CSS height (e.g., "100%", "500px") */
|
||||
/** CSS height value (e.g., "100%", "500px", "calc(100vh - 200px)") */
|
||||
height?: string;
|
||||
/** CSS width (e.g., "100%", "800px") */
|
||||
/** CSS width value (e.g., "100%", "800px") */
|
||||
width?: string;
|
||||
/** Callback when editor is ready with imperative handle */
|
||||
/** Called when the editor is fully initialized, providing the imperative handle */
|
||||
onReady?: (handle: EditorHandle) => void;
|
||||
/** Host-specific context (e.g., database info from SQL Lab) */
|
||||
/** Contextual data passed to completion providers (e.g., database ID, schema) */
|
||||
metadata?: Record<string, unknown>;
|
||||
/** Theme object for styling the editor */
|
||||
/** Theme object for styling the editor to match Superset's appearance */
|
||||
theme?: SupersetTheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imperative API for controlling the editor programmatically.
|
||||
*
|
||||
* This handle provides a unified interface for interacting with text editors
|
||||
* regardless of the underlying implementation (Ace, Monaco, CodeMirror, etc.).
|
||||
* It can be used by any part of Superset that needs to manipulate editor content,
|
||||
* read selections, or register custom behaviors.
|
||||
*/
|
||||
export interface EditorHandle {
|
||||
/** Focus the editor */
|
||||
focus(): void;
|
||||
/** Get the current editor content */
|
||||
getValue(): string;
|
||||
/** Set the editor content */
|
||||
setValue(value: string): void;
|
||||
/** Get the current cursor position */
|
||||
getCursorPosition(): Position;
|
||||
/** Move the cursor to a specific position */
|
||||
moveCursorToPosition(position: Position): void;
|
||||
/** Get all selections in the editor */
|
||||
getSelections(): Selection[];
|
||||
/** Set the selection range */
|
||||
setSelection(selection: Range): void;
|
||||
/** Get the selected text */
|
||||
getSelectedText(): string;
|
||||
/** Insert text at the current cursor position */
|
||||
insertText(text: string): void;
|
||||
/** Execute a named editor command */
|
||||
executeCommand(commandName: string): void;
|
||||
/** Scroll to a specific line */
|
||||
scrollToLine(line: number): void;
|
||||
/** Set annotations (replaces existing) */
|
||||
setAnnotations(annotations: EditorAnnotation[]): void;
|
||||
/** Clear all annotations */
|
||||
clearAnnotations(): void;
|
||||
/**
|
||||
* Register a completion provider for dynamic suggestions.
|
||||
* Moves keyboard focus to the editor.
|
||||
* Useful after programmatic operations to return user focus to the editing area.
|
||||
*/
|
||||
focus(): void;
|
||||
|
||||
/**
|
||||
* Returns the complete text content of the editor.
|
||||
* @returns The full editor content as a string
|
||||
*/
|
||||
getValue(): string;
|
||||
|
||||
/**
|
||||
* Replaces the entire editor content with the provided value.
|
||||
* This will clear any existing content and reset the undo history in most editors.
|
||||
* @param value The new content to set
|
||||
*/
|
||||
setValue(value: string): void;
|
||||
|
||||
/**
|
||||
* Returns the current cursor position in the editor.
|
||||
* @returns Position object with zero-based line and column numbers
|
||||
*/
|
||||
getCursorPosition(): Position;
|
||||
|
||||
/**
|
||||
* Moves the cursor to the specified position.
|
||||
* @param position Target position with zero-based line and column numbers
|
||||
*/
|
||||
moveCursorToPosition(position: Position): void;
|
||||
|
||||
/**
|
||||
* Returns all active selections in the editor.
|
||||
* Most editors support multiple selections (e.g., via Ctrl+click).
|
||||
* Each selection includes start/end positions and optional direction.
|
||||
* @returns Array of Selection objects, empty array if no selections
|
||||
*/
|
||||
getSelections(): Selection[];
|
||||
|
||||
/**
|
||||
* Sets the selection to the specified range.
|
||||
* This replaces any existing selections with a single new selection.
|
||||
* @param selection Range to select, with start and end positions
|
||||
*/
|
||||
setSelection(selection: Range): void;
|
||||
|
||||
/**
|
||||
* Returns the text within the current selection.
|
||||
* If multiple selections exist, behavior depends on the editor implementation
|
||||
* (typically returns the primary/first selection's text).
|
||||
* @returns The selected text, or empty string if no selection
|
||||
*/
|
||||
getSelectedText(): string;
|
||||
|
||||
/**
|
||||
* Inserts text at the current cursor position.
|
||||
* If text is selected, the selection is replaced with the inserted text.
|
||||
* @param text The text to insert
|
||||
*/
|
||||
insertText(text: string): void;
|
||||
/**
|
||||
* Execute a named editor command.
|
||||
*
|
||||
* Note: Command names are editor-specific. For example:
|
||||
* - Ace: 'centerselection', 'gotoline', 'fold', 'unfold'
|
||||
* - Monaco: 'editor.action.formatDocument', 'editor.action.commentLine'
|
||||
*
|
||||
* Callers using this method should be aware of which editor is active
|
||||
* or handle cases where the command may not exist.
|
||||
*
|
||||
* @param commandName The editor-specific command name to execute
|
||||
*/
|
||||
executeCommand(commandName: string): void;
|
||||
/**
|
||||
* Scrolls the editor viewport to bring the specified line into view.
|
||||
* The exact positioning (top, center, bottom) depends on the editor implementation.
|
||||
* @param line Zero-based line number to scroll to
|
||||
*/
|
||||
scrollToLine(line: number): void;
|
||||
|
||||
/**
|
||||
* Sets diagnostic annotations (errors, warnings, info markers) in the editor.
|
||||
* This replaces any previously set annotations.
|
||||
* Annotations appear as markers in the gutter and/or inline decorations.
|
||||
* @param annotations Array of annotations to display
|
||||
*/
|
||||
setAnnotations(annotations: EditorAnnotation[]): void;
|
||||
|
||||
/**
|
||||
* Removes all annotations from the editor.
|
||||
* Equivalent to calling setAnnotations([]).
|
||||
*/
|
||||
clearAnnotations(): void;
|
||||
|
||||
/**
|
||||
* Registers a provider for dynamic autocompletion suggestions.
|
||||
* The provider will be invoked when completion is triggered (manually or automatically).
|
||||
* Multiple providers can be registered; their results are merged.
|
||||
* @param provider The completion provider to register
|
||||
* @returns A Disposable to unregister the provider
|
||||
* @returns A Disposable that removes the provider when disposed
|
||||
*/
|
||||
registerCompletionProvider(provider: CompletionProvider): Disposable;
|
||||
}
|
||||
|
||||
@@ -30,44 +30,14 @@
|
||||
*/
|
||||
|
||||
import { Event, Database, SupersetError, Column } from './core';
|
||||
import { EditorHandle } from './editors';
|
||||
|
||||
/**
|
||||
* Represents an SQL editor instance within a SQL Lab tab.
|
||||
* Contains the editor content and associated database connection information.
|
||||
* Provides imperative control over the code editor component.
|
||||
* Allows extensions to manipulate text content, cursor position,
|
||||
* selections, annotations, and register completion providers.
|
||||
*/
|
||||
export interface Editor {
|
||||
/**
|
||||
* The SQL content of the editor.
|
||||
* This represents the current text in the SQL editor.
|
||||
*/
|
||||
content: string;
|
||||
|
||||
/**
|
||||
* The database identifier associated with the editor.
|
||||
* This determines which database the queries will be executed against.
|
||||
*/
|
||||
databaseId: number;
|
||||
|
||||
/**
|
||||
* The catalog name associated with the editor.
|
||||
* Can be null if no specific catalog is selected.
|
||||
*/
|
||||
catalog: string | null;
|
||||
|
||||
/**
|
||||
* The schema name associated with the editor.
|
||||
* Defines the database schema context for the editor.
|
||||
*/
|
||||
schema: string;
|
||||
|
||||
/**
|
||||
* The table name associated with the editor.
|
||||
* Can be null if no specific table is selected.
|
||||
*
|
||||
* @todo Revisit if we actually need the table property
|
||||
*/
|
||||
table: string | null;
|
||||
}
|
||||
export interface Editor extends EditorHandle {}
|
||||
|
||||
/**
|
||||
* Represents a panel within a SQL Lab tab.
|
||||
@@ -99,10 +69,40 @@ export interface Tab {
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* The SQL editor instance associated with this tab.
|
||||
* Contains the editor content and database connection settings.
|
||||
* The database identifier for this tab's query context.
|
||||
* This determines which database the queries will be executed against.
|
||||
*/
|
||||
editor: Editor;
|
||||
databaseId: number;
|
||||
|
||||
/**
|
||||
* The catalog name for this tab's query context.
|
||||
* Can be null if no specific catalog is selected (for multi-catalog databases like Trino).
|
||||
*/
|
||||
catalog: string | null;
|
||||
|
||||
/**
|
||||
* The schema name for this tab's query context.
|
||||
* Can be null if no schema is selected.
|
||||
*/
|
||||
schema: string | null;
|
||||
|
||||
/**
|
||||
* Gets the code editor instance for this tab.
|
||||
* Returns a Promise that resolves when the editor is ready.
|
||||
* The returned editor is a proxy that always delegates to the current
|
||||
* editor implementation, even if the editor is swapped (e.g., Ace to Monaco).
|
||||
*
|
||||
* @returns Promise that resolves to the Editor instance
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const tab = sqlLab.getCurrentTab();
|
||||
* const editor = await tab.getEditor();
|
||||
* editor.setValue("SELECT * FROM users");
|
||||
* editor.focus();
|
||||
* ```
|
||||
*/
|
||||
getEditor(): Promise<Editor>;
|
||||
|
||||
/**
|
||||
* The panels associated with the tab.
|
||||
@@ -262,7 +262,12 @@ export declare const getActivePanel: () => Panel;
|
||||
* const tab = getCurrentTab();
|
||||
* if (tab) {
|
||||
* console.log(`Active tab: ${tab.title}`);
|
||||
* console.log(`Database ID: ${tab.editor.databaseId}`);
|
||||
* console.log(`Database ID: ${tab.databaseId}, Schema: ${tab.schema}`);
|
||||
*
|
||||
* // Editor manipulation via async getEditor()
|
||||
* const editor = await tab.getEditor();
|
||||
* editor.setValue("SELECT * FROM users");
|
||||
* editor.focus();
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@@ -326,9 +331,10 @@ export declare const onDidChangeTabTitle: Event<string>;
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* onDidQueryRun.event((query) => {
|
||||
* console.log('Query started on database:', query.tab.editor.databaseId);
|
||||
* console.log('Query content:', query.tab.editor.content);
|
||||
* onDidQueryRun.event(async (query) => {
|
||||
* console.log('Query started on database:', query.tab.databaseId);
|
||||
* const editor = await query.tab.getEditor();
|
||||
* console.log('Query SQL:', editor.getValue());
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
@@ -341,7 +347,7 @@ export declare const onDidQueryRun: Event<QueryContext>;
|
||||
* @example
|
||||
* ```typescript
|
||||
* onDidQueryStop.event((query) => {
|
||||
* console.log('Query stopped for database:', query.tab.editor.databaseId);
|
||||
* console.log('Query stopped for database:', query.tab.databaseId);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
@@ -444,3 +450,253 @@ export declare const onDidCloseTab: Event<Tab>;
|
||||
* ```
|
||||
*/
|
||||
export declare const onDidChangeActiveTab: Event<Tab>;
|
||||
|
||||
/**
|
||||
* Event fired when a new tab is created in SQL Lab.
|
||||
* Provides the newly created tab object as the event payload.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* onDidCreateTab.event((tab) => {
|
||||
* console.log('New tab created:', tab.title);
|
||||
* // Initialize extension state for new tab
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export declare const onDidCreateTab: Event<Tab>;
|
||||
|
||||
/**
|
||||
* Tab/Editor Management APIs
|
||||
*
|
||||
* These APIs allow extensions to create, close, and manage SQL Lab tabs.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for creating a new SQL Lab tab.
|
||||
*/
|
||||
export interface CreateTabOptions {
|
||||
/**
|
||||
* Initial SQL content for the editor.
|
||||
*/
|
||||
sql?: string;
|
||||
|
||||
/**
|
||||
* Display title for the tab.
|
||||
* If not provided, defaults to "Untitled Query N".
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* Database ID to connect to.
|
||||
* If not provided, inherits from the active tab or uses default.
|
||||
*/
|
||||
databaseId?: number;
|
||||
|
||||
/**
|
||||
* Catalog name (for multi-catalog databases like Trino).
|
||||
*/
|
||||
catalog?: string | null;
|
||||
|
||||
/**
|
||||
* Schema name for the query context.
|
||||
*/
|
||||
schema?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new query editor tab in SQL Lab.
|
||||
*
|
||||
* @param options Optional configuration for the new tab
|
||||
* @returns The newly created tab object
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Create a tab with default settings
|
||||
* const tab = await createTab();
|
||||
*
|
||||
* // Create a tab with specific SQL and database
|
||||
* const tab = await createTab({
|
||||
* sql: "SELECT * FROM users LIMIT 10",
|
||||
* title: "User Query",
|
||||
* databaseId: 1,
|
||||
* schema: "public"
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export declare function createTab(options?: CreateTabOptions): Promise<Tab>;
|
||||
|
||||
/**
|
||||
* Closes a specific tab in SQL Lab.
|
||||
*
|
||||
* @param tabId The ID of the tab to close
|
||||
* @returns Promise that resolves when the tab is closed
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const tabs = getTabs();
|
||||
* if (tabs.length > 1) {
|
||||
* await closeTab(tabs[0].id);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export declare function closeTab(tabId: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Switches to a specific tab in SQL Lab.
|
||||
*
|
||||
* @param tabId The ID of the tab to activate
|
||||
* @returns Promise that resolves when the tab is activated
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const tabs = getTabs();
|
||||
* const targetTab = tabs.find(t => t.title === "My Query");
|
||||
* if (targetTab) {
|
||||
* await setActiveTab(targetTab.id);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export declare function setActiveTab(tabId: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Query Execution APIs
|
||||
*
|
||||
* These APIs allow extensions to execute and control SQL queries.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for executing a SQL query.
|
||||
*/
|
||||
export interface QueryOptions {
|
||||
/**
|
||||
* SQL to execute without modifying editor content.
|
||||
* If not provided, uses the current editor content.
|
||||
*/
|
||||
sql?: string;
|
||||
|
||||
/**
|
||||
* Run only the selected text in the editor.
|
||||
* Ignored if `sql` option is provided.
|
||||
*/
|
||||
selectedOnly?: boolean;
|
||||
|
||||
/**
|
||||
* Override the query row limit.
|
||||
* If not provided, uses the tab's configured limit.
|
||||
*/
|
||||
limit?: number;
|
||||
|
||||
/**
|
||||
* Template parameters for Jinja templating.
|
||||
* Merged with existing template parameters from the editor.
|
||||
*/
|
||||
templateParameters?: Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* Create Table/View As Select options.
|
||||
* When provided, query results are stored in a new table instead of returned directly.
|
||||
*/
|
||||
ctas?: {
|
||||
/**
|
||||
* Whether to create a TABLE or VIEW.
|
||||
*/
|
||||
method: 'TABLE' | 'VIEW';
|
||||
|
||||
/**
|
||||
* Name of the table or view to create.
|
||||
*/
|
||||
tableName: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a SQL query in the current tab.
|
||||
*
|
||||
* @param options Optional query execution options
|
||||
* @returns Promise that resolves with the query ID
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Execute the current editor content
|
||||
* const queryId = await executeQuery();
|
||||
*
|
||||
* // Execute custom SQL without modifying the editor
|
||||
* const queryId = await executeQuery({
|
||||
* sql: "SELECT * FROM users LIMIT 10"
|
||||
* });
|
||||
*
|
||||
* // Execute only selected text
|
||||
* const queryId = await executeQuery({ selectedOnly: true });
|
||||
*
|
||||
* // Create a table from query results
|
||||
* const queryId = await executeQuery({
|
||||
* ctas: { method: 'TABLE', tableName: 'my_results' }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export declare function executeQuery(options?: QueryOptions): Promise<string>;
|
||||
|
||||
/**
|
||||
* Cancels a running query.
|
||||
*
|
||||
* @param queryId The client ID of the query to cancel
|
||||
* @returns Promise that resolves when the cancellation request is sent
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const queryId = await executeQuery();
|
||||
* // Later, if needed:
|
||||
* await cancelQuery(queryId);
|
||||
* ```
|
||||
*/
|
||||
export declare function cancelQuery(queryId: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Tab Context APIs
|
||||
*
|
||||
* These APIs manage tab-level query context and settings.
|
||||
* Text manipulation is handled directly via Editor (e.g., tab.editor.setValue(sql)).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the database for the current tab.
|
||||
*
|
||||
* @param databaseId The ID of the database to set
|
||||
* @returns Promise that resolves when the database is updated
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const databases = getDatabases();
|
||||
* const prodDb = databases.find(d => d.database_name === "production");
|
||||
* if (prodDb) {
|
||||
* await setDatabase(prodDb.id);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export declare function setDatabase(databaseId: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* Sets the catalog for the current tab.
|
||||
*
|
||||
* @param catalog The catalog name to set, or null to clear
|
||||
* @returns Promise that resolves when the catalog is updated
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* await setCatalog("hive_metastore");
|
||||
* ```
|
||||
*/
|
||||
export declare function setCatalog(catalog: string | null): Promise<void>;
|
||||
|
||||
/**
|
||||
* Sets the schema for the current tab.
|
||||
*
|
||||
* @param schema The schema name to set, or null to clear
|
||||
* @returns Promise that resolves when the schema is updated
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* await setSchema("public");
|
||||
* ```
|
||||
*/
|
||||
export declare function setSchema(schema: string | null): Promise<void>;
|
||||
|
||||
@@ -202,7 +202,7 @@ test('serializeThemeConfig defaults to "default" for unknown algorithms', () =>
|
||||
const unknownAlgorithm = () => ({});
|
||||
const config: AntdThemeConfig = {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
algorithm: unknownAlgorithm,
|
||||
};
|
||||
|
||||
@@ -237,7 +237,7 @@ test('serializeThemeConfig defaults each unknown algorithm in array to "default"
|
||||
const unknownAlgorithm = () => ({});
|
||||
const config: AntdThemeConfig = {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
algorithm: [antdThemeImport.darkAlgorithm, unknownAlgorithm],
|
||||
};
|
||||
|
||||
@@ -257,10 +257,10 @@ test('serializeThemeConfig handles mixed known and unknown algorithms in array',
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
algorithm: [
|
||||
antdThemeImport.darkAlgorithm,
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
unknownAlgorithm1,
|
||||
antdThemeImport.compactAlgorithm,
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
unknownAlgorithm2,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -29,8 +29,9 @@ import {
|
||||
FieldStringOutlined,
|
||||
NumberOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Icons } from '@superset-ui/core/components';
|
||||
|
||||
export type ColumnLabelExtendedType = 'expression' | '';
|
||||
export type ColumnLabelExtendedType = 'expression' | 'metric' | '';
|
||||
|
||||
export type ColumnTypeLabelProps = {
|
||||
type?: ColumnLabelExtendedType | GenericDataType;
|
||||
@@ -59,7 +60,9 @@ export function ColumnTypeLabel({ type }: ColumnTypeLabelProps) {
|
||||
<QuestionOutlined aria-label={t('unknown type icon')} />
|
||||
);
|
||||
|
||||
if (type === '' || type === 'expression') {
|
||||
if (type === 'metric') {
|
||||
typeIcon = <Icons.Sigma aria-label={t('metric type icon')} />;
|
||||
} else if (type === '' || type === 'expression') {
|
||||
typeIcon = <FunctionOutlined aria-label={t('function type icon')} />;
|
||||
} else if (type === GenericDataType.String) {
|
||||
typeIcon = <FieldStringOutlined aria-label={t('string type icon')} />;
|
||||
|
||||
@@ -95,7 +95,7 @@ export function MetricOption({
|
||||
|
||||
return (
|
||||
<FlexRowContainer className="metric-option">
|
||||
{showType && <ColumnTypeLabel type="expression" />}
|
||||
{showType && <ColumnTypeLabel type="metric" />}
|
||||
{shouldShowTooltip ? (
|
||||
<Tooltip id="metric-name-tooltip" title={tooltipText}>
|
||||
{label}
|
||||
|
||||
@@ -23,7 +23,7 @@ import { ControlPanelSectionConfig } from '../types';
|
||||
import { formatSelectOptions } from '../utils';
|
||||
|
||||
export const TITLE_MARGIN_OPTIONS: number[] = [
|
||||
15, 30, 50, 75, 100, 125, 150, 200,
|
||||
0, 15, 30, 50, 75, 100, 125, 150, 200,
|
||||
];
|
||||
export const TITLE_POSITION_OPTIONS: [string, string][] = [
|
||||
['Left', t('Left')],
|
||||
@@ -82,7 +82,7 @@ export const titleControls: ControlPanelSectionConfig = {
|
||||
clearable: true,
|
||||
label: t('Y Axis Title Margin'),
|
||||
renderTrigger: true,
|
||||
default: TITLE_MARGIN_OPTIONS[1],
|
||||
default: TITLE_MARGIN_OPTIONS[0],
|
||||
choices: formatSelectOptions(TITLE_MARGIN_OPTIONS),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -52,6 +52,10 @@ describe('ColumnOption', () => {
|
||||
renderColumnTypeLabel({ type: 'expression' });
|
||||
expect(screen.getByLabelText('function type icon')).toBeVisible();
|
||||
});
|
||||
it('metric type shows sigma icon', () => {
|
||||
renderColumnTypeLabel({ type: 'metric' });
|
||||
expect(screen.getByLabelText('metric type icon')).toBeVisible();
|
||||
});
|
||||
it('unknown type shows question mark', () => {
|
||||
renderColumnTypeLabel({ type: undefined });
|
||||
expect(screen.getByLabelText('unknown type icon')).toBeVisible();
|
||||
|
||||
@@ -129,7 +129,7 @@ test('returns empty array if timeseries_limit_metric is an empty array', () => {
|
||||
expect(
|
||||
extractExtraMetrics({
|
||||
...baseFormData,
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
timeseries_limit_metric: [],
|
||||
}),
|
||||
).toEqual([]);
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('defineSavedMetrics', () => {
|
||||
uuid: '1',
|
||||
},
|
||||
]);
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
expect(defineSavedMetrics({ ...dataset, metrics: undefined })).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
@@ -306,7 +306,7 @@ test('getColorFunction BETWEEN with target value right undefined', () => {
|
||||
test('getColorFunction unsupported operator', () => {
|
||||
const colorFunction = getColorFunction(
|
||||
{
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
operator: 'unsupported operator',
|
||||
targetValue: 50,
|
||||
colorScheme: '#FF0000',
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"classnames": "^2.5.1",
|
||||
"csstype": "^3.2.3",
|
||||
"core-js": "^3.48.0",
|
||||
"d3-format": "^1.3.2",
|
||||
"d3-format": "^3.1.2",
|
||||
"dayjs": "^1.11.19",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
@@ -78,14 +78,14 @@
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/jquery": "^3.5.33",
|
||||
"@types/lodash": "^4.17.23",
|
||||
"@types/node": "^25.0.10",
|
||||
"@types/node": "^25.2.1",
|
||||
"@types/prop-types": "^15.7.15",
|
||||
"@types/rison": "0.1.0",
|
||||
"@types/seedrandom": "^3.0.8",
|
||||
"fetch-mock": "^11.1.4",
|
||||
"fetch-mock": "^12.6.0",
|
||||
"jest-mock-console": "^2.0.0",
|
||||
"resize-observer-polyfill": "1.5.1",
|
||||
"timezone-mock": "1.3.6"
|
||||
"timezone-mock": "1.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"antd": "^5.26.0",
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
import { t } from '@apache-superset/core';
|
||||
import { SupersetTheme } from '@apache-superset/core/ui';
|
||||
import { FallbackPropsWithDimension } from './SuperChart';
|
||||
import { getErrorMessage } from 'react-error-boundary';
|
||||
|
||||
export type Props = Partial<FallbackPropsWithDimension>;
|
||||
|
||||
@@ -39,7 +38,13 @@ export default function FallbackComponent({ error, height, width }: Props) {
|
||||
<div>
|
||||
<b>{t('Oops! An error occurred!')}</b>
|
||||
</div>
|
||||
<code>{error ? getErrorMessage(error) : 'Unknown Error'}</code>
|
||||
<code>
|
||||
{error instanceof Error
|
||||
? error.message
|
||||
: error
|
||||
? String(error)
|
||||
: t('Unknown Error')}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -119,7 +119,7 @@ export function AsyncEsmComponent<
|
||||
const Component = component || placeholder;
|
||||
return Component ? (
|
||||
// placeholder does not get the ref
|
||||
// @ts-ignore: Suppress TypeScript error for ref assignment
|
||||
// @ts-expect-error: Suppress TypeScript error for ref assignment
|
||||
<Component ref={Component === component ? ref : null} {...props} />
|
||||
) : null;
|
||||
});
|
||||
|
||||
@@ -126,7 +126,7 @@ export function Button(props: ButtonProps) {
|
||||
minWidth: cta ? theme.sizeUnit * 36 : undefined,
|
||||
minHeight: cta ? theme.sizeUnit * 8 : undefined,
|
||||
marginLeft: 0,
|
||||
'& + .superset-button': {
|
||||
'& + .superset-button:not(.ant-btn-compact-item)': {
|
||||
marginLeft: theme.sizeUnit * 2,
|
||||
},
|
||||
'& > span > :first-of-type': {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useState, useRef, useCallback } from 'react';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { Divider } from '../Divider';
|
||||
import { Input } from '../Input';
|
||||
import { CronPicker } from '.';
|
||||
@@ -28,22 +28,19 @@ export default {
|
||||
};
|
||||
|
||||
export const InteractiveCronPicker = (props: CronProps) => {
|
||||
// @ts-ignore
|
||||
const inputRef = useRef<Input>(null);
|
||||
const [value, setValue] = useState(props.value);
|
||||
const customSetValue = useCallback(
|
||||
(newValue: string) => {
|
||||
setValue(newValue);
|
||||
inputRef.current?.setValue(newValue);
|
||||
},
|
||||
[inputRef],
|
||||
);
|
||||
useEffect(() => {
|
||||
setValue(props.value);
|
||||
}, [props.value]);
|
||||
const customSetValue = useCallback((newValue: string) => {
|
||||
setValue(newValue);
|
||||
}, []);
|
||||
const [error, onError] = useState<CronError>();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={value}
|
||||
onBlur={event => {
|
||||
setValue(event.target.value);
|
||||
}}
|
||||
|
||||
@@ -26,7 +26,7 @@ const props = {
|
||||
describe('NoAnimationDropdown', () => {
|
||||
it('requires children', () => {
|
||||
expect(() => {
|
||||
// @ts-ignore need to test the error case
|
||||
// @ts-expect-error need to test the error case
|
||||
render(<NoAnimationDropdown {...props} />);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
@@ -17,13 +17,14 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { render, fireEvent, screen } from '@superset-ui/core/spec';
|
||||
import type { LabeledErrorBoundInputProps } from './types';
|
||||
import { LabeledErrorBoundInput } from './LabeledErrorBoundInput';
|
||||
|
||||
const defaultProps = {
|
||||
id: 1,
|
||||
const defaultProps: LabeledErrorBoundInputProps = {
|
||||
id: '1',
|
||||
label: 'Username',
|
||||
name: 'Username',
|
||||
validationMethods: () => {},
|
||||
validationMethods: { onBlur: () => {} },
|
||||
errorMessage: '',
|
||||
helpText: 'This is a line of example help text',
|
||||
hasTooltip: false,
|
||||
@@ -76,6 +76,10 @@ import {
|
||||
FileOutlined,
|
||||
FileTextOutlined,
|
||||
FireOutlined,
|
||||
FolderAddOutlined,
|
||||
FolderOpenOutlined,
|
||||
FolderOutlined,
|
||||
FolderViewOutlined,
|
||||
FormOutlined,
|
||||
FullscreenExitOutlined,
|
||||
FullscreenOutlined,
|
||||
@@ -94,15 +98,18 @@ import {
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
MinusCircleOutlined,
|
||||
MinusSquareOutlined,
|
||||
MoonOutlined,
|
||||
LoadingOutlined,
|
||||
LoginOutlined,
|
||||
MonitorOutlined,
|
||||
MoreOutlined,
|
||||
OrderedListOutlined,
|
||||
PartitionOutlined,
|
||||
PieChartOutlined,
|
||||
PicCenterOutlined,
|
||||
PlusCircleOutlined,
|
||||
PlusSquareOutlined,
|
||||
PlusOutlined,
|
||||
ProfileOutlined,
|
||||
QuestionCircleOutlined,
|
||||
@@ -217,6 +224,10 @@ const AntdIcons = {
|
||||
FileOutlined,
|
||||
FileTextOutlined,
|
||||
FireOutlined,
|
||||
FolderAddOutlined,
|
||||
FolderOpenOutlined,
|
||||
FolderOutlined,
|
||||
FolderViewOutlined,
|
||||
FormOutlined,
|
||||
FullscreenExitOutlined,
|
||||
FullscreenOutlined,
|
||||
@@ -240,13 +251,16 @@ const AntdIcons = {
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
MinusCircleOutlined,
|
||||
MinusSquareOutlined,
|
||||
MonitorOutlined,
|
||||
MoonOutlined,
|
||||
MoreOutlined,
|
||||
OrderedListOutlined,
|
||||
PartitionOutlined,
|
||||
PieChartOutlined,
|
||||
PicCenterOutlined,
|
||||
PlusCircleOutlined,
|
||||
PlusSquareOutlined,
|
||||
PlusOutlined,
|
||||
ProfileOutlined,
|
||||
ReloadOutlined,
|
||||
|
||||
@@ -42,10 +42,12 @@ const customIcons = [
|
||||
'Error',
|
||||
'Full',
|
||||
'Layers',
|
||||
'Move',
|
||||
'Multiple',
|
||||
'Queued',
|
||||
'Redo',
|
||||
'Running',
|
||||
'Sigma',
|
||||
'Slack',
|
||||
'Square',
|
||||
'SortAsc',
|
||||
|
||||
@@ -36,7 +36,7 @@ test('works with an onClick handler', () => {
|
||||
|
||||
// test stories from the storybook!
|
||||
test('renders all the storybook gallery variants', () => {
|
||||
// @ts-ignore: Suppress TypeScript error for LabelGallery usage
|
||||
// @ts-expect-error: Suppress TypeScript error for LabelGallery usage
|
||||
const { container } = render(<LabelGallery />);
|
||||
const nonInteractiveLabelCount = 4;
|
||||
const renderedLabelCount = options.length * 2 + nonInteractiveLabelCount;
|
||||
|
||||
@@ -24,12 +24,18 @@ import { ImageLoader, type BackgroundPosition } from './ImageLoader';
|
||||
global.URL.createObjectURL = jest.fn(() => '/local_url');
|
||||
const blob = new Blob([], { type: 'image/png' });
|
||||
|
||||
beforeAll(() => {
|
||||
fetchMock.mockGlobal();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
fetchMock.hardReset();
|
||||
});
|
||||
|
||||
fetchMock.get(
|
||||
'/thumbnail',
|
||||
'glob:*/thumbnail',
|
||||
{ body: blob, headers: { 'Content-Type': 'image/png' } },
|
||||
{
|
||||
sendAsJson: false,
|
||||
},
|
||||
{ name: 'thumbnail' },
|
||||
);
|
||||
|
||||
describe('ImageLoader', () => {
|
||||
@@ -44,7 +50,7 @@ describe('ImageLoader', () => {
|
||||
return render(<ImageLoader {...props} />);
|
||||
};
|
||||
|
||||
afterEach(() => fetchMock.resetHistory());
|
||||
afterEach(() => fetchMock.clearHistory());
|
||||
|
||||
it('is a valid element', async () => {
|
||||
setup();
|
||||
@@ -57,7 +63,7 @@ describe('ImageLoader', () => {
|
||||
'src',
|
||||
'/fallback',
|
||||
);
|
||||
expect(fetchMock.calls(/thumbnail/)).toHaveLength(1);
|
||||
expect(fetchMock.callHistory.calls(/thumbnail/)).toHaveLength(1);
|
||||
expect(global.URL.createObjectURL).toHaveBeenCalled();
|
||||
expect(await screen.findByTestId('image-loader')).toHaveAttribute(
|
||||
'src',
|
||||
@@ -66,13 +72,14 @@ describe('ImageLoader', () => {
|
||||
});
|
||||
|
||||
it('displays fallback image when response is not an image', async () => {
|
||||
fetchMock.once('/thumbnail2', {});
|
||||
setup({ src: '/thumbnail2' });
|
||||
fetchMock.once('glob:*/thumbnail2', {}, { name: 'thumbnail2' });
|
||||
|
||||
setup({ src: 'glob:*/thumbnail2' });
|
||||
expect(screen.getByTestId('image-loader')).toHaveAttribute(
|
||||
'src',
|
||||
'/fallback',
|
||||
);
|
||||
expect(fetchMock.calls(/thumbnail2/)).toHaveLength(1);
|
||||
expect(fetchMock.callHistory.calls(/thumbnail2/)).toHaveLength(1);
|
||||
expect(await screen.findByTestId('image-loader')).toHaveAttribute(
|
||||
'src',
|
||||
'/fallback',
|
||||
|
||||
@@ -479,10 +479,10 @@ const AsyncSelect = forwardRef(
|
||||
fullSelectOptions.filter(opt => set.has(opt.value)),
|
||||
);
|
||||
if (isSingleMode) {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
onChange?.(selectValue, options[0]);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
onChange?.(array, options);
|
||||
}
|
||||
}
|
||||
@@ -619,7 +619,7 @@ const AsyncSelect = forwardRef(
|
||||
onBlur={handleOnBlur}
|
||||
onDeselect={handleOnDeselect}
|
||||
onOpenChange={handleOnDropdownVisibleChange}
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
onPaste={onPaste}
|
||||
onPopupScroll={handlePagination}
|
||||
onSearch={showSearch ? handleOnSearch : undefined}
|
||||
|
||||
@@ -748,7 +748,7 @@ const Select = forwardRef(
|
||||
onBlur={handleOnBlur}
|
||||
onDeselect={handleOnDeselect}
|
||||
onOpenChange={handleOnDropdownVisibleChange}
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
onPaste={onPaste}
|
||||
onPopupScroll={undefined}
|
||||
onSearch={shouldShowSearch ? handleOnSearch : undefined}
|
||||
|
||||
@@ -45,20 +45,20 @@ const rows = [
|
||||
* 1 or greater means the first item comes before the second item
|
||||
*/
|
||||
test('alphabeticalSort sorts correctly', () => {
|
||||
// @ts-ignore
|
||||
expect(alphabeticalSort('name', rows[0], rows[1])).toBe(-1);
|
||||
// @ts-ignore
|
||||
expect(alphabeticalSort('name', rows[1], rows[0])).toBe(1);
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
expect(alphabeticalSort('name', rows[0], rows[1])).toBeLessThan(0);
|
||||
// @ts-expect-error
|
||||
expect(alphabeticalSort('name', rows[1], rows[0])).toBeGreaterThan(0);
|
||||
// @ts-expect-error
|
||||
expect(alphabeticalSort('category', rows[1], rows[0])).toBe(0);
|
||||
});
|
||||
|
||||
test('numericalSort sorts correctly', () => {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
expect(numericalSort('cost', rows[1], rows[2])).toBe(0);
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
expect(numericalSort('cost', rows[1], rows[0])).toBeLessThan(0);
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
expect(numericalSort('cost', rows[4], rows[1])).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
@@ -68,10 +68,10 @@ test('numericalSort sorts correctly', () => {
|
||||
* In the case the sorter cannot perform the comparison it should return undefined and the next sort step will proceed without error
|
||||
*/
|
||||
test('alphabeticalSort bad inputs no errors', () => {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
expect(alphabeticalSort('name', null, null)).toBe(undefined);
|
||||
// incorrect non-object values
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
expect(alphabeticalSort('name', 3, [])).toBe(undefined);
|
||||
// incorrect object values without specified key
|
||||
expect(alphabeticalSort('name', {}, {})).toBe(undefined);
|
||||
@@ -79,7 +79,7 @@ test('alphabeticalSort bad inputs no errors', () => {
|
||||
expect(
|
||||
alphabeticalSort(
|
||||
'name',
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
{ name: { title: 'the name attribute should not be an object' } },
|
||||
{ name: 'Doug' },
|
||||
),
|
||||
@@ -87,22 +87,22 @@ test('alphabeticalSort bad inputs no errors', () => {
|
||||
});
|
||||
|
||||
test('numericalSort bad inputs no errors', () => {
|
||||
// @ts-ignore
|
||||
expect(numericalSort('name', undefined, undefined)).toBe(NaN);
|
||||
// @ts-ignore
|
||||
expect(numericalSort('name', null, null)).toBe(NaN);
|
||||
// @ts-expect-error
|
||||
expect(numericalSort('name', undefined, undefined)).toBeNaN();
|
||||
// @ts-expect-error
|
||||
expect(numericalSort('name', null, null)).toBeNaN();
|
||||
// incorrect non-object values
|
||||
// @ts-ignore
|
||||
expect(numericalSort('name', 3, [])).toBe(NaN);
|
||||
// @ts-expect-error
|
||||
expect(numericalSort('name', 3, [])).toBeNaN();
|
||||
// incorrect object values without specified key
|
||||
expect(numericalSort('name', {}, {})).toBe(NaN);
|
||||
expect(numericalSort('name', {}, {})).toBeNaN();
|
||||
// Object as value for name when it should be a string
|
||||
expect(
|
||||
numericalSort(
|
||||
'name',
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
{ name: { title: 'the name attribute should not be an object' } },
|
||||
{ name: 'Doug' },
|
||||
),
|
||||
).toBe(NaN);
|
||||
).toBeNaN();
|
||||
});
|
||||
|
||||
@@ -46,12 +46,12 @@ test('withinRange unsupported negative numbers', async () => {
|
||||
test('withinRange invalid inputs', async () => {
|
||||
// Invalid inputs should return falsy and not throw an error
|
||||
// We need ts-ignore here to be able to pass invalid values and pass linting
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
expect(withinRange(null, 60, undefined)).toBeFalsy();
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
expect(withinRange([], 'hello', {})).toBeFalsy();
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
expect(withinRange([], undefined, {})).toBeFalsy();
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
expect(withinRange([], 'hello', {})).toBeFalsy();
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ beforeEach(() => {
|
||||
parent: { child: 'Nested Value 3' },
|
||||
},
|
||||
];
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const tableHookResult = renderHook(() => useTable({ columns, data }));
|
||||
tableHook = tableHookResult.result.current;
|
||||
defaultProps = {
|
||||
|
||||
@@ -47,7 +47,6 @@ export const TelemetryPixel = ({
|
||||
const pixelPath = `https://apachesuperset.gateway.scarf.sh/pixel/${PIXEL_ID}/${version}/${sha}/${build}`;
|
||||
return process.env.SCARF_ANALYTICS === 'false' ? null : (
|
||||
<img
|
||||
// @ts-ignore
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
src={pixelPath}
|
||||
width={0}
|
||||
|
||||
@@ -241,7 +241,7 @@ test('uses queueMicrotask when available', async () => {
|
||||
|
||||
test('falls back to setTimeout when queueMicrotask is not available', async () => {
|
||||
const originalQueueMicrotask = global.queueMicrotask;
|
||||
// @ts-ignore - temporarily remove queueMicrotask for testing
|
||||
// @ts-expect-error - temporarily remove queueMicrotask for testing
|
||||
delete global.queueMicrotask;
|
||||
|
||||
const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
|
||||
|
||||
@@ -109,7 +109,7 @@ export function evalExpression(expression: string, value: number): number {
|
||||
parsedExpression = subExpressions[1] ?? subExpressions[0];
|
||||
// we can ignore the type requirement on `TOKENS`, as value is always `number`
|
||||
// and doesn't need to consider `number | undefined`.
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
return Number(mexp.eval(parsedExpression, TOKENS, { x: value }));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
*/
|
||||
|
||||
export default class ExtensibleFunction extends Function {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
constructor(fn: Function) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, no-constructor-return
|
||||
return Object.setPrototypeOf(fn, new.target.prototype);
|
||||
|
||||
@@ -23,4 +23,5 @@ export const DEFAULT_D3_FORMAT: FormatLocaleDefinition = {
|
||||
thousands: ',',
|
||||
grouping: [3],
|
||||
currency: ['$', ''],
|
||||
minus: '-', // Use ASCII hyphen for backward compatibility (d3-format v3 defaults to Unicode minus sign)
|
||||
};
|
||||
|
||||
@@ -16,14 +16,11 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
format as d3Format,
|
||||
formatLocale,
|
||||
FormatLocaleDefinition,
|
||||
} from 'd3-format';
|
||||
import { formatLocale, FormatLocaleDefinition } from 'd3-format';
|
||||
import { isRequired } from '../../utils';
|
||||
import NumberFormatter from '../NumberFormatter';
|
||||
import { NumberFormatFunction } from '../types';
|
||||
import { DEFAULT_D3_FORMAT } from '../D3FormatConfig';
|
||||
|
||||
export default function createD3NumberFormatter(config: {
|
||||
description?: string;
|
||||
@@ -42,10 +39,7 @@ export default function createD3NumberFormatter(config: {
|
||||
let isInvalid = false;
|
||||
|
||||
try {
|
||||
formatFunc =
|
||||
typeof locale === 'undefined'
|
||||
? d3Format(formatString)
|
||||
: formatLocale(locale).format(formatString);
|
||||
formatFunc = formatLocale(locale ?? DEFAULT_D3_FORMAT).format(formatString);
|
||||
} catch (error) {
|
||||
formatFunc = value => `${value} (Invalid format: ${formatString})`;
|
||||
isInvalid = true;
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { format as d3Format } from 'd3-format';
|
||||
import { formatLocale } from 'd3-format';
|
||||
import NumberFormatter from '../NumberFormatter';
|
||||
import { DEFAULT_D3_FORMAT } from '../D3FormatConfig';
|
||||
|
||||
export default function createSiAtMostNDigitFormatter(
|
||||
config: {
|
||||
@@ -29,7 +30,8 @@ export default function createSiAtMostNDigitFormatter(
|
||||
} = {},
|
||||
) {
|
||||
const { description, n = 3, id, label } = config;
|
||||
const siFormatter = d3Format(`.${n}s`);
|
||||
const locale = formatLocale(DEFAULT_D3_FORMAT);
|
||||
const siFormatter = locale.format(`.${n}s`);
|
||||
|
||||
return new NumberFormatter({
|
||||
description,
|
||||
|
||||
@@ -17,13 +17,15 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { format as d3Format } from 'd3-format';
|
||||
import { formatLocale } from 'd3-format';
|
||||
import NumberFormatter from '../NumberFormatter';
|
||||
import NumberFormats from '../NumberFormats';
|
||||
import { DEFAULT_D3_FORMAT } from '../D3FormatConfig';
|
||||
|
||||
const siFormatter = d3Format(`.3~s`);
|
||||
const float2PointFormatter = d3Format(`.2~f`);
|
||||
const float4PointFormatter = d3Format(`.4~f`);
|
||||
const locale = formatLocale(DEFAULT_D3_FORMAT);
|
||||
const siFormatter = locale.format(`.3~s`);
|
||||
const float2PointFormatter = locale.format(`.2~f`);
|
||||
const float4PointFormatter = locale.format(`.4~f`);
|
||||
|
||||
function formatValue(value: number) {
|
||||
if (value === 0) {
|
||||
|
||||
@@ -63,7 +63,6 @@ export default function normalizeOrderBy(
|
||||
) {
|
||||
return {
|
||||
...cloneQueryObject,
|
||||
// @ts-ignore
|
||||
orderby: [[queryObject.legacy_order_by, isAsc]],
|
||||
};
|
||||
}
|
||||
|
||||