Compare commits

..

219 Commits

Author SHA1 Message Date
Evan Rusackas
9b8f2f3f94 fix(tests): update UTC and Pacific timezone test expectations for timezone-mock 1.4.0
The previous commit addressed Etc/GMT-2 timezone test expectations.
This commit updates the remaining failing tests for UTC and Etc/GMT+8
(Pacific) timezone cases in parseDttmToDate.test.ts to match the new
behavior of timezone-mock 1.4.0.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-07 10:06:51 -08:00
Evan Rusackas
a3d876c64f fix(tests): update time-comparison test expectations for timezone-mock 1.4.0
timezone-mock 1.4.0 changed the fromLocal algorithm to follow the
EcmaScript specification for resolving local times to UTC timestamps.
This affects Etc/GMT-2 timezone test cases where the local-to-UTC
conversion now produces different results. Updated all Etc/GMT-2
expected values in getTimeOffset.test.ts and parseDttmToDate.test.ts
to match the new behavior.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-07 10:01:16 -08:00
Evan Rusackas
c0763fe58f fix(tests): update timezone-mock usage for v1.4.0 compatibility
In timezone-mock 1.4.0, the timezone must be registered BEFORE setting
the system time via jest.setSystemTime(). The previous order (setting
time first, then registering timezone) caused date calculations to be
off by 1 day.

This fixes test failures in:
- packages/superset-ui-core/test/time-comparison/getTimeOffset.test.ts
- packages/superset-ui-core/test/time-comparison/parseDttmToDate.test.ts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-07 10:01:16 -08:00
dependabot[bot]
2f2ce330b2 chore(deps-dev): bump timezone-mock in /superset-frontend
Bumps [timezone-mock](https://github.com/Jimbly/timezone-mock) from 1.3.6 to 1.4.0.
- [Commits](https://github.com/Jimbly/timezone-mock/commits)

---
updated-dependencies:
- dependency-name: timezone-mock
  dependency-version: 1.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-07 10:01:15 -08:00
Rini Misini
f4708a5648 fix(db): prevent long database error messages from overflowing UI (#37709)
Co-authored-by: RiniMisini12 <misinirini@gmail.com>
2026-02-07 21:13:09 +07:00
dependabot[bot]
b9ab03994a chore(deps-dev): bump jsdom from 27.4.0 to 28.0.0 in /superset-frontend (#37688)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-07 15:32:38 +07:00
dependabot[bot]
df253f6aa4 chore(deps-dev): bump @babel/plugin-transform-runtime from 7.28.5 to 7.29.0 in /superset-frontend (#37631)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-07 00:12:35 -08:00
dependabot[bot]
5cea4fb7fe chore(deps): update @luma.gl/shadertools requirement from ~9.2.5 to ~9.2.6 in /superset-frontend/plugins/legacy-preset-chart-deckgl (#37763)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 23:55:22 -08:00
dependabot[bot]
76a27d5360 chore(deps): bump d3-format from 1.4.5 to 3.1.2 in /superset-frontend/packages/superset-ui-core (#37442)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 23:42:39 -08:00
dependabot[bot]
174e3c26d3 chore(deps): update @luma.gl/webgl requirement from ~9.2.5 to ~9.2.6 in /superset-frontend/plugins/legacy-preset-chart-deckgl (#37764)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 23:32:37 -08:00
Alexandru Soare
9ea5ded988 fix(dashboard): Prevent fatal error when database connection is unavailable (#37576) 2026-02-06 20:52:17 -08:00
Đỗ Trọng Hải
9086ae8e6c feat(ci): only bump patch version for Storybook-related deps until React 18 (#37749)
Signed-off-by: hainenber <dotronghai96@gmail.com>
2026-02-06 13:29:32 -08:00
Evan Rusackas
fc5506e466 chore(frontend): comprehensive TypeScript quality improvements (#37625)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 13:16:57 -08:00
Abhishek Mishra
e9ae212c1c fix(alerts): show screenshot width field for PDF reports (#37037) 2026-02-06 11:19:18 -08:00
Evan Rusackas
46bca32677 docs(seo): add structured data, OpenGraph tags, and sitemap improvements (#37404)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 11:09:19 -08:00
JUST.in DO IT
a04571fa20 fix(world-map): reset hover highlight on mouse out (#37716)
Co-authored-by: Arunodoy18 <arunodoy630@gmail.com>
2026-02-06 10:27:57 -08:00
Evan Rusackas
fc26dbfebf chore(deps): upgrade deck.gl and luma.gl packages to ~9.2.6 (#37718)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 10:20:50 -08:00
Michael S. Molina
0415118544 chore: Bump @apache-superset/core (0.0.1-rc10) (#37759) 2026-02-06 14:18:22 -03:00
Michael S. Molina
935bbe6061 docs: Updates extensions docs (#37704) 2026-02-06 13:18:25 -03:00
Daniel Vaz Gaspar
ec6eaf4898 fix(deps): bump elasticsearch-dbapi to 0.2.12 for urllib3 2.x compatibility (#37758)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 16:03:04 +00:00
dependabot[bot]
87d15d32c4 chore(deps-dev): bump @types/node from 25.2.0 to 25.2.1 in /superset-frontend (#37732)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 20:16:18 +07:00
dependabot[bot]
7d9a8a0c5a chore(deps-dev): bump @babel/node from 7.28.6 to 7.29.0 in /superset-frontend (#37734)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 20:15:47 +07:00
dependabot[bot]
ddba88ffad chore(deps): bump googleapis from 171.2.0 to 171.4.0 in /superset-frontend (#37736)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 20:15:29 +07:00
Evan Rusackas
1e50422a66 chore: remove deprecated react-hot-loader (#36433)
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-06 15:57:31 +07:00
Evan Rusackas
246dbd7f5c chore(deps): upgrade react-resize-detector to v9.1.1 (#37741)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 15:25:30 +07:00
dependabot[bot]
9b861b2848 chore(deps): bump caniuse-lite from 1.0.30001768 to 1.0.30001769 in /docs (#37726)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 14:33:57 +07:00
dependabot[bot]
1c35c3f6d0 chore(deps): bump markdown-to-jsx from 9.7.2 to 9.7.3 in /superset-frontend (#37730)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 14:31:39 +07:00
dependabot[bot]
b71654877f chore(deps-dev): bump @types/node from 25.2.0 to 25.2.1 in /superset-websocket (#37719)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 14:31:09 +07:00
dependabot[bot]
cd447ca1fd chore(deps): update @luma.gl/webgl requirement from ~9.2.2 to ~9.2.6 in /superset-frontend/plugins/legacy-preset-chart-deckgl (#37469)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 22:39:25 -08:00
Amin Ghadersohi
01ac966b83 fix(mcp): remove html.escape to fix ampersand display in chart titles (#37186)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 19:49:37 -08:00
dependabot[bot]
97e5f0631d chore(deps): bump aws-actions/configure-aws-credentials from 5 to 6 (#37685)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-05 19:48:51 -08:00
dependabot[bot]
b7acb7984f chore(deps-dev): bump @babel/core from 7.28.6 to 7.29.0 in /superset-frontend (#37686)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-05 19:48:43 -08:00
Evan Rusackas
d3919cf24f fix(translations): Periodic language strings extraction, newly Translatable label positions for Radar Chart (#33940)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 19:48:24 -08:00
dependabot[bot]
27889651b3 chore(deps): bump markdown-to-jsx from 9.6.1 to 9.7.2 in /superset-frontend (#37691)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 09:55:51 +07:00
Đỗ Trọng Hải
361fe6fe89 chore(build): add @hainenber as codeowner for GHA workflow changes (#37703)
Signed-off-by: hainenber <dotronghai96@gmail.com>
2026-02-06 09:54:21 +07:00
dependabot[bot]
8506d70242 chore(deps-dev): bump webpack from 5.94.0 to 5.105.0 in /superset-embedded-sdk (#37713)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 09:53:35 +07:00
Evan Rusackas
00a53eec2d fix(translations): remove corrupted text from Spanish translation file (#37717)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 09:52:14 +07:00
Joe Li
5040db859c test(playwright): additional dataset list playwright tests (#36684)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-05 16:42:07 -08:00
Evan Rusackas
ef4f7afa90 chore(docs): improve build performance and fix OOM crashes (#37588)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 13:12:46 -08:00
Amin Ghadersohi
47db185e3b fix(mcp): include x_axis column in query context for series charts with group_by (#37639) 2026-02-05 19:59:44 +01:00
Joe Li
2e463078a2 refactor(filters): extract shouldShowTimeRangePicker and improve test coverage (#36012)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-05 10:48:55 -08:00
JUST.in DO IT
4f42928b34 fix(sqllab): Skip progress bar on no data (#37652) 2026-02-05 10:38:37 -08:00
Gabriel Torres Ruiz
75fa474fce test(native-filters): add unit tests for requiredFirst filter logic (#37640) 2026-02-05 10:36:35 -08:00
dependabot[bot]
fd8c21591a chore(deps-dev): update @babel/types requirement from ^7.28.6 to ^7.29.0 in /superset-frontend/plugins/plugin-chart-pivot-table (#37603)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
2026-02-05 10:31:56 -08:00
Amin Ghadersohi
4147d877fc fix(mcp): prevent DATE_TRUNC on non-temporal columns in chart generation (#37433) 2026-02-05 09:24:31 -08:00
Amin Ghadersohi
a9dca529c1 fix(mcp): treat runtime validation warnings as informational, not errors (#37214)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 09:23:51 -08:00
dependabot[bot]
20f1918dd6 chore(deps): bump caniuse-lite from 1.0.30001767 to 1.0.30001768 in /docs (#37684)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-05 22:26:53 +07:00
dependabot[bot]
c09a4f6f47 chore(deps): bump googleapis from 171.1.0 to 171.2.0 in /superset-frontend (#37690)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-05 21:37:26 +07:00
bikashbarua
4e4fa53c8d fix: Rename Truncate Axis to Truncate Y Axis in bar chart controls (#37403)
Co-authored-by: Joe Li <joe@preset.io>
Co-authored-by: SBIN2010 <Sbin2010@mail.ru>
2026-02-05 12:55:51 +03:00
Miguel
07ff82f189 docs: add XNET to INTHEWILD list (#37615)
Co-authored-by: Miguel Deus <miguel@xnet.company>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-02-04 21:27:35 -08:00
dependabot[bot]
b7b9bfd3fe chore(deps): bump query-string from 6.14.1 to 9.3.1 in /superset-frontend (#37545)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
2026-02-04 21:26:28 -08:00
dependabot[bot]
b968d1095c chore(deps): bump dawidd6/action-download-artifact from 12 to 14 (#37602)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-04 21:25:52 -08:00
Michael S. Molina
e10237fcc1 fix: Security vulnerability in Storybook (#37676) 2026-02-04 14:48:39 -03:00
Michael S. Molina
92438322c0 feat(extensions): Enhances SQL Lab API (#37642) 2026-02-04 13:53:58 -03:00
Đỗ Trọng Hải
f96e90b979 fix(docker): remove accidental command substitutions when building FE in dev mode (#37670) 2026-02-04 23:53:20 +07:00
dependabot[bot]
b464979db1 chore(deps-dev): bump webpack from 5.104.1 to 5.105.0 in /superset-frontend (#37658)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-04 22:47:12 +07:00
dependabot[bot]
45f883c9cd chore(deps-dev): bump webpack from 5.104.1 to 5.105.0 in /docs (#37656)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-04 21:35:51 +07:00
Nancy Chauhan
8fd3401077 fix(security): update jspdf to 4.0.0 to address CVE-2025-68428 (#37553) 2026-02-04 21:29:57 +07:00
Richard Fogaca Nienkotter
89a98ab9a4 fix(dataset-editor): include calculated columns in currency code dropdown (#37621) 2026-02-04 11:17:07 -03:00
Jamile Celento
2dfc770b0f fix(native-filters): update TEMPORAL_RANGE filter subject when Time Column filter is applied (#36985) 2026-02-04 12:37:17 +03:00
Vanessa Giannoni
6b7b23ed78 fix(timeseries): restore ECharts tooltip after closing drill menu (#37284) 2026-02-04 12:32:23 +03:00
dependabot[bot]
5ac5480f35 chore(deps): bump caniuse-lite from 1.0.30001766 to 1.0.30001767 in /docs (#37601)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-03 12:35:32 -08:00
Evan Rusackas
76889c1a69 feat(db_engine_specs): add Apache Phoenix and Apache IoTDB engine specs (#37590)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 15:29:19 -05:00
Evan Rusackas
569606635b docs(databases): add Supabase, AlloyDB, and Neon as PostgreSQL-compatible databases (#37589)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 12:22:49 -08:00
dependabot[bot]
66264856a7 chore(deps): bump googleapis from 171.0.0 to 171.1.0 in /superset-frontend (#37630)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-03 22:28:07 +07:00
dependabot[bot]
3eb860a663 chore(deps): bump hot-shots from 13.1.0 to 13.2.0 in /superset-websocket (#37596)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-03 22:19:07 +07:00
dependabot[bot]
a44980da65 chore(deps-dev): bump globals from 17.2.0 to 17.3.0 in /superset-websocket (#37595)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-03 22:18:33 +07:00
dependabot[bot]
7112bce961 chore(deps-dev): bump @types/node from 25.1.0 to 25.2.0 in /superset-websocket (#37597)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-03 22:18:14 +07:00
dependabot[bot]
568486a304 chore(deps): bump @babel/core from 7.28.6 to 7.29.0 in /docs (#37598)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-03 22:17:50 +07:00
dependabot[bot]
fea135b46c chore(deps-dev): bump @playwright/test from 1.58.0 to 1.58.1 in /superset-frontend (#37633)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-03 22:17:13 +07:00
dependabot[bot]
601fcb3382 chore(deps-dev): bump @babel/preset-env from 7.28.6 to 7.29.0 in /superset-frontend (#37635)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-03 22:02:46 +07:00
dependabot[bot]
0d7cc88b2b chore(deps): bump antd from 6.2.2 to 6.2.3 in /docs (#37629)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-03 21:50:13 +07:00
Vitor Avila
32ee160c75 chore: Properly untrack WebSocket config file for docker (#37624) 2026-02-03 11:48:08 -03:00
Amin Ghadersohi
5914e83436 chore(mcp): remove unused MCP_SERVICE feature flag (#37618) 2026-02-03 15:23:08 +01:00
Amin Ghadersohi
0b5e4dd5de feat(mcp): add config toggle to disable parse_request decorator (#37617)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 15:22:44 +01:00
Joe Li
3a565a6c16 fix(tests): update DatasetList tests to new fetch-mock API (#37623)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 14:15:58 +03:00
Ramiro Aquino Romero
f60c82e4a6 fix: charts row limit warning is missing for server (#37112) 2026-02-02 15:49:31 -08:00
Luis Sánchez
91131d5996 chore(charts): echarts left padding too big and automation of title (#36993) 2026-02-02 15:48:03 -08:00
Joe Li
4b0d497513 test: add new RTL and integration tests for DatasetList (#36681)
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-02 12:08:38 -08:00
Joe Li
86f690d17f fix(dashboard): fix Export as Example with app prefix and enable Dashboard Export E2E tests (#37529)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 12:07:22 -08:00
Elizabeth Thompson
e9b494163b refactor(db): use Dialect instead of Engine in select_star to avoid SSH tunnels (#35540)
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-02 10:26:35 -08:00
JUST.in DO IT
be404f9b84 fix(dashboard): Avoid calling loadData for invisible charts on virtual rendering (#37452) 2026-02-02 10:07:25 -08:00
Daniel Vaz Gaspar
11257c0536 fix(examples): skip URI safety check for system imports (#37577)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 09:24:16 -08:00
Beto Dealmeida
f2b6c395cd feat: Add PWA file handler for CSV/XLS/Parquet uploads (#36191)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 11:24:01 -05:00
dependabot[bot]
2d35ed2391 chore(deps-dev): bump @babel/runtime-corejs3 from 7.28.6 to 7.29.0 in /superset-frontend (#37605)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-02 23:03:59 +07:00
dependabot[bot]
bd65469091 chore(deps-dev): bump globals from 17.2.0 to 17.3.0 in /docs (#37599)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-02 21:48:25 +07:00
Kamil Gabryjelski
a6a66ca483 feat: Dataset folders editor (#36239) 2026-02-02 14:54:33 +01:00
Jonathan Alberth Quispe Fuentes
4a7cdccdad fix: Heatmap does not render correctly on normalization (#37208) 2026-02-02 12:34:46 +03:00
dependabot[bot]
61bd8f0cf2 chore(deps): bump use-query-params from 1.2.3 to 2.2.2 in /superset-frontend (#36997)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
2026-02-01 23:55:39 -08:00
Evan Rusackas
ae10e105c2 fix(chart): enable cross-filter on bar charts without dimensions (#37407)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 17:14:24 -08:00
dependabot[bot]
901dca58f7 chore(deps): bump JustinBeckwith/linkinator-action from 2.3 to 2.4 (#37562)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-31 03:06:30 -08:00
dependabot[bot]
d95a3d8426 chore(deps-dev): bump @applitools/eyes-storybook from 3.63.9 to 3.63.10 in /superset-frontend (#37566)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-31 03:06:09 -08:00
alok kumar priyadarshi
70b95ca1b9 fix(build): eliminate PostgreSQL extra installation on Python 3.12-based Superset Docker images (#37587) 2026-01-31 15:54:19 +07:00
Michael S. Molina
004f02746f fix(build): Increase ForkTsCheckerWebpackPlugin memory limit to fix OOM error (#37583) 2026-01-31 14:22:17 +07:00
Beto Dealmeida
5d20dc57d7 feat(oauth2): add PKCE support for database OAuth2 authentication (#37067) 2026-01-30 23:28:10 -05:00
Beto Dealmeida
05c2354997 feat: AWS Cross-Account IAM Authentication for Aurora (#37585) 2026-01-30 19:18:34 -05:00
Vitor Avila
6043e7e7e3 fix: more DB OAuth2 fixes (#37398) 2026-01-30 21:11:26 -03:00
Amin Ghadersohi
1ee14c5993 fix(mcp): improve prompts, resources, and instructions clarity (#37389) 2026-01-30 12:25:38 -08:00
Felipe López
9764a84402 fix(charts): Table chart shows an error on row limit (#37218) 2026-01-30 11:45:50 -08:00
JUST.in DO IT
570cc3e5f8 feat(sqllab): treeview table selection ui (#37298) 2026-01-30 11:07:56 -08:00
dependabot[bot]
66519c3a85 chore(deps-dev): bump fetch-mock from 11.1.5 to 12.6.0 in /superset-frontend/packages/superset-ui-core (#36662)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
2026-01-30 21:27:35 +07:00
dependabot[bot]
1f43138888 chore(deps): bump babel-loader from 9.2.1 to 10.0.0 in /docs (#37541)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-30 21:06:23 +07:00
dependabot[bot]
652d029a2d chore(deps-dev): bump @types/node from 25.0.10 to 25.1.0 in /superset-frontend (#37563)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-30 21:03:26 +07:00
dependabot[bot]
e67b1f5326 chore(deps-dev): bump baseline-browser-mapping from 2.9.18 to 2.9.19 in /superset-frontend (#37565)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-30 20:56:46 +07:00
dependabot[bot]
fa79a467e4 chore(deps): bump googleapis from 170.1.0 to 171.0.0 in /superset-frontend (#37564)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-30 16:57:04 +07:00
Pedro Rodrigues
2cce0308d4 fix: big number drill to details column data (#37068) 2026-01-30 12:32:49 +03:00
dependabot[bot]
c7fd1a2f65 chore(deps-dev): bump @types/node from 25.0.10 to 25.1.0 in /superset-websocket (#37539)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-30 10:22:41 +07:00
dependabot[bot]
ab4f646ef6 chore(deps): bump @babel/core from 7.28.5 to 7.28.6 in /docs (#37540)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-30 10:22:15 +07:00
Alejandro Solares
d6029f5c8a chore(deps): bump dependencies to address security vulnerabilities (#37552) 2026-01-30 10:19:43 +07:00
dependabot[bot]
c16e8f747c chore(deps-dev): bump css-loader from 7.1.2 to 7.1.3 in /superset-frontend (#37544)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-30 10:18:20 +07:00
Evan Rusackas
9c0337d092 fix(explore): correct validationDependancies typo to validationDependencies (#37554)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 01:18:50 +03:00
Evan Rusackas
3ef33dcb76 feat(playwright): add documentation screenshot generator (#37494)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 10:14:53 -08:00
Jean Massucatto
5a99588f57 fix(themes): correct action icons size and restore missing tooltips (#37409) 2026-01-29 20:42:15 +03:00
Kamil Gabryjelski
0b34363654 fix(mcp): Instance not bound to session error (#37548) 2026-01-29 18:00:34 +01:00
Jean Massucatto
55ec1152ec fix(deckgl): change deck gl Path default line width unit to meters (#37248) 2026-01-29 19:58:50 +03:00
Michael S. Molina
810d6ff480 fix(sqllab): Resolve stale closure bug causing text selection to break (#37550) 2026-01-29 13:09:26 -03:00
Mehmet Salih Yavuz
1501af06fe fix(Multilayer): preserve dashboard context for embedded (#37495) 2026-01-29 18:06:12 +03:00
Michael S. Molina
6cb3cea960 feat(extensions): Allow replacing editors using extensions (#37499) 2026-01-29 08:22:32 -03:00
Felipe López
675a4c7a66 fix(charts): numerical column for the Point Radius field in mapbox (#36962) 2026-01-29 10:50:10 +01:00
Vanessa Giannoni
7110fc9cde fix(explore): remove extra spacing when Advanced Analytics section is hidden (#37456) 2026-01-29 12:18:06 +03:00
Evan Rusackas
73e095db8e docs(components): federate Storybook stories into Developer Portal MDX (#37502)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 21:33:01 -08:00
Evan Rusackas
5fedb65bc0 fix(docs): migrate deprecated antd v6 APIs to items prop pattern (#37530)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 18:47:20 -08:00
Evan Rusackas
b3526fc4ca docs(community): redesign community page with card grid layout (#37536)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 17:58:05 -08:00
Evan Rusackas
042229bf80 fix(docs): add consistent dev-mode logging for Matomo page views (#37526)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 17:54:38 -08:00
Gabriel Torres Ruiz
06e4f4ff4c fix(dashboard): catch DatasourceNotFound in get_datasets to prevent 404 (#37503) 2026-01-28 15:54:56 -08:00
yousoph
bb5be6cf54 fix(matrixify): Rename Tag from 'Matrixify' to 'Matrixified' (#37402)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-28 15:46:51 -08:00
Evan Rusackas
f6f9e083ac fix(docs): replace identicon logos and deduplicate README database wall (#37500)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 12:33:36 -08:00
Evan Rusackas
ad0186093f docs: add interactive API reference using docusaurus-openapi-docs (#37434)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 11:16:25 -08:00
Evan Rusackas
073c3c72b4 docs: add HPE logo to In the Wild listing (#37506)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 14:03:46 -05:00
dependabot[bot]
d4b89de001 chore(deps-dev): bump oxlint from 1.41.0 to 1.42.0 in /superset-frontend (#37512)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-28 22:09:04 +07:00
SkinnyPigeon
912538d176 docs: Updating quickstart for 6.0.0 (#37446) 2026-01-28 22:08:31 +07:00
dependabot[bot]
cfeb7ccd31 chore(deps-dev): bump globals from 17.1.0 to 17.2.0 in /superset-websocket (#37508)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-28 22:06:13 +07:00
Jean Massucatto
abf90de0ca fix(select): prevent bulk action buttons from being cut off in filters (#37453) 2026-01-28 17:16:14 +03:00
Vanessa Giannoni
ec2509a8b4 fix(dashboard-filters): prevent clearing all filters when editing a native filter (#37253) 2026-01-28 17:06:00 +03:00
Reynold Morel
43653d1fa1 fix(dashboard): resolve dropdown popup positioning (#36963) 2026-01-28 17:01:37 +03:00
dependabot[bot]
da56bddada chore(deps-dev): bump globals from 17.1.0 to 17.2.0 in /docs (#37509)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-28 20:46:08 +07:00
Jamile Celento
3347b9bf6c fix(table): only show increase/decrease color options when time comparison enabled (#37362) 2026-01-28 13:32:30 +01:00
Amin Ghadersohi
6663709a23 fix(mcp): tools not listed when JWT auth is enabled (#37377) 2026-01-28 11:20:20 +01:00
Amin Ghadersohi
e6d0f97aab fix(mcp): always filter list responses by columns_requested (#37505) 2026-01-28 11:15:19 +01:00
dependabot[bot]
3bcd3b1683 chore(deps-dev): bump typescript-eslint from 8.53.1 to 8.54.0 in /superset-websocket (#37466)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-28 07:55:13 +07:00
dependabot[bot]
b223f10ab5 chore(deps-dev): bump @typescript-eslint/eslint-plugin from 8.53.1 to 8.54.0 in /superset-websocket (#37468)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-28 07:35:46 +07:00
Evan Rusackas
f787aec567 docs: add Netlify to CI services footer and improve layout (#37451)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 14:41:38 -08:00
Vanessa Giannoni
2ec3aaaeea feat(dashboard): show dataset column labels in View as table (#37140) 2026-01-27 13:51:28 -08:00
JUST.in DO IT
20da4eb86e fix(sqllab): Over-rendering on result table (#30857) 2026-01-27 13:47:58 -08:00
Mehmet Salih Yavuz
27a4575f3e refactor: Move frontend related artifacts into frontend gitignore (#37496) 2026-01-27 22:38:11 +03:00
yousoph
5fa6925522 fix(dashboard): update chart customization UI text to "Display controls" (#37462)
Co-authored-by: Maxime Beauchemin <maximebeauchemin@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-27 10:27:14 -08:00
Vanessa Giannoni
a7e7cc30a9 fix(ag-grid-table): preserve time grain aggregation when temporal column casing changes (#36990) 2026-01-27 10:13:32 -08:00
Evan Rusackas
e4d71c2a55 docs(readme): sync database logos with auto-generated docs (#37463)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 09:58:03 -08:00
dependabot[bot]
10a8d8b8ee chore(deps-dev): bump @typescript-eslint/parser from 8.53.1 to 8.54.0 in /superset-websocket (#37465)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-27 09:57:29 -08:00
Mehmet Salih Yavuz
1681f74b2e chore(direnv): add direnv to gitignore (#37481) 2026-01-27 19:14:50 +03:00
dependabot[bot]
58ab4e78ff chore(deps): bump antd from 6.2.1 to 6.2.2 in /docs (#37470)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-27 23:11:13 +07:00
dependabot[bot]
8f6dd4aba0 chore(deps-dev): bump typescript-eslint from 8.53.1 to 8.54.0 in /docs (#37476)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-27 23:07:16 +07:00
dependabot[bot]
dba75bd897 chore(deps): bump memoize-one from 5.2.1 to 6.0.0 in /superset-frontend/packages/superset-ui-demo (#37151)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 22:22:00 -08:00
dependabot[bot]
e28d2782f1 chore(deps): bump core-js from 3.40.0 to 3.48.0 in /superset-frontend/packages/superset-ui-demo (#37439)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
2026-01-26 21:58:16 -08:00
alex
97aea5d128 feat(i18n): complete Māori translation (#37443)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-26 21:40:49 -08:00
dependabot[bot]
bd419d19af chore(deps): update core-js requirement from ^3.38.1 to ^3.48.0 in /superset-frontend/packages/superset-ui-core (#37319)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 17:15:22 -08:00
dependabot[bot]
56ad429200 chore(deps): update @luma.gl/shadertools requirement from ~9.2.2 to ~9.2.6 in /superset-frontend/plugins/legacy-preset-chart-deckgl (#37237)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 17:14:36 -08:00
Evan Rusackas
7fc9974a7c fix(deps): remove encodable dependency and pin query-string to fix Dependabot CI failures (#37450)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 15:52:22 -08:00
Peng Ren
73d4332b51 feat(database): SIP-195 Add MongoDB database engine support (#37368)
Co-authored-by: Peng Ren <ia250@cummins.com>
2026-01-26 15:51:35 -08:00
Tilak Mahajan
10a9b4bb94 fix: update Apache Superset website footer copyright year (#37435) 2026-01-26 15:51:14 -08:00
Daniel Vaz Gaspar
290bcc1dbb feat(cache): use configurable hash algorithm for flask-caching (#37361) 2026-01-26 10:19:51 -08:00
dependabot[bot]
26ac832138 chore(deps): update @luma.gl/engine requirement from ~9.2.4 to ~9.2.6 in /superset-frontend/plugins/legacy-preset-chart-deckgl (#37440)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
2026-01-26 09:37:11 -08:00
JUST.in DO IT
4db6f9e04c chore(trino): Add progress_text for additional running state info (#36909) 2026-01-26 09:11:33 -08:00
JUST.in DO IT
0fd528c7af feat(sqllab): Improved query status indicator bar (#36936) 2026-01-26 08:57:52 -08:00
dependabot[bot]
647f21c26a chore(deps): bump hot-shots from 13.0.0 to 13.1.0 in /superset-websocket (#37436)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-26 08:34:51 -08:00
dependabot[bot]
89b998d6b7 chore(deps): bump react-intersection-observer from 10.0.0 to 10.0.2 in /superset-frontend (#37338)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-26 08:22:36 -08:00
dependabot[bot]
695e295333 chore(deps): bump ag-grid-community from 34.3.1 to 35.0.1 in /superset-frontend (#37386)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 08:22:15 -08:00
dependabot[bot]
f2fc5dec11 chore(deps): bump ag-grid-react from 34.3.1 to 35.0.1 in /superset-frontend (#37419)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 08:21:50 -08:00
Michael S. Molina
95a465ad7c feat(sqllab): add getActivePanel API for panel visibility tracking (#37448) 2026-01-26 13:18:01 -03:00
Ramiro Aquino Romero
8aebfe1105 fix: display correct icon for Multi Chart in quick switcher (#37256) 2026-01-26 14:47:39 +03:00
dependabot[bot]
c7cec19827 chore(deps): bump ace-builds from 1.43.5 to 1.43.6 in /superset-frontend (#37424)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-25 22:14:18 +07:00
dependabot[bot]
ce84ab4ce2 chore(deps-dev): bump cheerio from 1.1.2 to 1.2.0 in /superset-frontend (#37423)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-25 22:13:19 +07:00
dependabot[bot]
470c593c3d chore(deps-dev): bump @types/node from 25.0.9 to 25.0.10 in /superset-frontend (#37418)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-25 19:50:15 +07:00
dependabot[bot]
04a9be04ab chore(deps-dev): bump @playwright/test from 1.57.0 to 1.58.0 in /superset-frontend (#37425)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-25 19:49:05 +07:00
dependabot[bot]
09b5af5945 chore(deps): bump markdown-to-jsx from 9.6.0 to 9.6.1 in /superset-frontend (#37420)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-25 19:48:34 +07:00
dependabot[bot]
19d5fa86fc chore(deps-dev): bump baseline-browser-mapping from 2.9.17 to 2.9.18 in /superset-frontend (#37426)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-25 19:48:00 +07:00
Yousuf Ansari
b09e60c1ec fix(deckgl-contour): prevent WebGL freeze by clamping and auto-scaling cellSize (#37244) 2026-01-24 11:18:41 -08:00
dependabot[bot]
2f81720603 chore(deps-dev): bump @swc/plugin-transform-imports from 10.0.0 to 12.4.0 in /superset-frontend (#37384)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
2026-01-24 11:18:10 -08:00
Amin Ghadersohi
0ecc69d2f1 chore(deps): bump fastmcp from 2.14.0 to 2.14.3 (#37410) 2026-01-24 07:03:00 -08:00
Felipe López
319a131ec9 fix(charts): missing globalOpacity prop with mapbox (#37168) 2026-01-23 15:08:16 -08:00
isaac-jaynes-imperva
3f37cdbf9c fix(database): include configuration_method in the DB export/import flow (#36958)
Co-authored-by: codeant-ai-for-open-source[bot] <244253245+codeant-ai-for-open-source[bot]@users.noreply.github.com>
2026-01-23 19:34:52 -03:00
dependabot[bot]
3a811d680d chore(deps): bump lodash from 4.17.21 to 4.17.23 in /superset-frontend (#37348)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 14:31:30 -08:00
dependabot[bot]
a60f8d761d chore(deps-dev): bump npm from 11.5.2 to 11.8.0 in /superset-frontend (#37352)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 14:31:15 -08:00
Amy Li
3580dc6cad chore(ts): Migrate Divider.jsx to Divider.tsx [SIP-36] (#36335)
Co-authored-by: Mona Lisa <dana.wan0409@gmail.com>
2026-01-23 14:30:03 -08:00
Reynold Morel
b99fc582e4 fix(chart): implement geohash decoding (#37027) 2026-01-23 14:15:29 -08:00
Martyn Gigg
e4f649e49c fix(superset-frontend): Fixes for broken functionality when an application root is defined (#36058) 2026-01-23 14:13:48 -08:00
Elena Felder
d54e227e25 chore: update old MotherDuck duckdb version to follow the official duckdb one (#36834) 2026-01-23 16:25:17 -05:00
Đỗ Trọng Hải
39ebf7a7ad chore(websocket): sync Node version to LTS v22 (#37102)
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: codeant-ai-for-open-source[bot] <244253245+codeant-ai-for-open-source[bot]@users.noreply.github.com>
2026-01-23 12:59:19 -08:00
Christian Geier
34418d7e0b fix(datasets): respect application root in database management link (#36986) 2026-01-23 12:58:50 -08:00
dependabot[bot]
d6328fcb42 chore(deps): bump mapbox-gl from 3.18.0 to 3.18.1 in /superset-frontend (#37382)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 12:57:59 -08:00
Jean Massucatto
e8363cf606 fix(redshift): normalize table names to lowercase for CSV uploads (#37019) 2026-01-23 12:40:38 -08:00
om pharate
5747fb1e85 feat(ListView): add pagination to card view and center row count display (#36288) 2026-01-23 12:40:07 -08:00
SBIN2010
d823dfd2b9 feat: add interactive column sorting to pivot table (#36050) 2026-01-23 11:10:13 -08:00
Ramiro Aquino Romero
baaa8c5f54 feat(deckgl): add auto zoom option in deck gl multi layer (#37221) 2026-01-23 11:08:58 -08:00
Pádraic Slattery
429d9b27f6 chore: Update outdated GitHub Actions version (#37305) 2026-01-23 11:07:42 -08:00
dependabot[bot]
56cf7a810b chore(deps-dev): bump html-webpack-plugin from 5.6.5 to 5.6.6 in /superset-frontend (#37392)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 11:07:05 -08:00
dependabot[bot]
bbab86a0b1 chore(deps-dev): bump @applitools/eyes-storybook from 3.63.8 to 3.63.9 in /superset-frontend (#37390)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 11:03:24 -08:00
dependabot[bot]
f83f952221 chore(deps-dev): bump webpack-bundle-analyzer from 5.1.1 to 5.2.0 in /superset-frontend (#37388)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 11:03:10 -08:00
dependabot[bot]
e14931c368 chore(deps-dev): bump swc-loader from 0.2.6 to 0.2.7 in /superset-frontend (#37387)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 11:02:55 -08:00
dependabot[bot]
8951362852 chore(deps-dev): bump globals from 17.0.0 to 17.1.0 in /docs (#37385)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 11:02:40 -08:00
dependabot[bot]
eeb4065d7d chore(deps): bump unist-util-visit from 5.0.0 to 5.1.0 in /docs (#37383)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 11:02:23 -08:00
dependabot[bot]
6a46700721 chore(deps): bump caniuse-lite from 1.0.30001765 to 1.0.30001766 in /docs (#37381)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 11:01:39 -08:00
dependabot[bot]
790b79541b chore(deps-dev): bump globals from 17.0.0 to 17.1.0 in /superset-websocket (#37380)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 11:01:24 -08:00
dependabot[bot]
7c69ec7f24 chore(deps): bump markdown-to-jsx from 7.7.4 to 9.6.0 in /superset-frontend (#37354)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 11:01:10 -08:00
dependabot[bot]
ef395662aa chore(deps-dev): bump @babel/runtime-corejs3 from 7.28.4 to 7.28.6 in /superset-frontend (#37353)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 10:55:45 -08:00
dependabot[bot]
e1ce553b2b chore(deps-dev): bump @types/node from 25.0.9 to 25.0.10 in /superset-websocket (#37351)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 10:55:20 -08:00
dependabot[bot]
b81543c18c chore(deps): bump lodash from 4.17.21 to 4.17.23 in /superset-frontend/cypress-base (#37349)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 10:55:06 -08:00
dependabot[bot]
5f67fa45ce chore(deps): bump jquery from 3.7.1 to 4.0.0 in /superset-frontend (#37342)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 10:54:38 -08:00
dependabot[bot]
8e0c584a92 chore(deps-dev): bump prettier from 3.8.0 to 3.8.1 in /superset-frontend (#37339)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 10:53:56 -08:00
dependabot[bot]
01a9541a0e chore(deps): bump dawidd6/action-download-artifact from 6 to 12 (#37324)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 10:53:34 -08:00
dependabot[bot]
5e3acc2041 chore(deps): bump actions/upload-artifact from 4 to 6 (#37320)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 10:53:15 -08:00
Daniel Vaz Gaspar
f2b54e882d fix: variable shadowing in test_connection command (#37397) 2026-01-23 16:27:51 +00:00
Ramiro Aquino Romero
54919c942a fix(time-range-modal): time range modal for out of scope filter is not displayed correctly (#36996) 2026-01-23 11:34:50 +02:00
Jean Massucatto
04c5517206 fix(dataset-editor): add missing Data type label in calculated columns tab (#37165) 2026-01-22 21:57:16 -08:00
Dheeraj Bansal
b1ad54220b fix: add sans-serif font fallback to fontFamily (Fixes #37096) (#37172) 2026-01-22 21:56:04 -08:00
dependabot[bot]
c6821cac6f chore(deps-dev): bump prettier from 3.8.0 to 3.8.1 in /superset-websocket (#37325)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-22 21:39:53 -08:00
Evan Rusackas
b7a5b24a54 feat(docs): add auto-generated troubleshooting section to database pages (#37345)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 14:43:02 -08:00
Gabriel Torres Ruiz
760227d630 fix(theme): migrate APP_NAME to brandAppName theme token with backward compatibility (#37370)
Co-authored-by: Rafael Benitez <rebenitez1802@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>
Co-authored-by: codeant-ai-for-open-source[bot] <244253245+codeant-ai-for-open-source[bot]@users.noreply.github.com>
2026-01-22 14:35:55 -08:00
ellion90
3ca8c998ab docs(INTHEWILD): add club 25 de agosto (#37110) 2026-01-22 17:30:03 -05:00
1088 changed files with 92231 additions and 28412 deletions

2
.github/CODEOWNERS vendored
View File

@@ -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

View File

@@ -10,7 +10,7 @@ jobs:
steps:
- name: Check if the PR is a draft
id: check-draft
uses: actions/github-script@v6
uses: actions/github-script@v8
with:
script: |
const isDraft = context.payload.pull_request.draft;

View File

@@ -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"
@@ -19,6 +22,18 @@ updates:
# Source: https://jestjs.io/blog#known-issues
# GH thread: https://github.com/jsdom/jsdom/issues/3492
- dependency-name: "jest-environment-jsdom"
# `@swc/plugin-transform-imports` doesn't work with current Webpack-SWC hybrid setup
# 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"
@@ -351,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:

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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.

View File

@@ -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@v6
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@v6
uses: dawidd6/action-download-artifact@v14
continue-on-error: true
with:
workflow: superset-python-integrationtest.yml

View File

@@ -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@v6
uses: dawidd6/action-download-artifact@v14
with:
workflow: superset-python-integrationtest.yml
run_id: ${{ github.event.workflow_run.id }}

View File

@@ -98,7 +98,7 @@ jobs:
"
- name: Upload database diagnostics artifact
if: steps.check.outputs.python
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: database-diagnostics
path: databases-diagnostics.json

5
.gitignore vendored
View File

@@ -61,6 +61,7 @@ tmp
rat-results.txt
superset/app/
superset-websocket/config.json
.direnv
# Node.js, webpack artifacts, storybook
*.entry.js
@@ -72,10 +73,6 @@ superset/static/assets/*
superset/static/uploads/*
!superset/static/uploads/.gitkeep
superset/static/version_info.json
superset-frontend/**/esm/*
superset-frontend/**/lib/*
superset-frontend/**/storybook-static/*
superset-frontend/migration-storybook.log
yarn-error.log
*.map
*.min.js

View File

@@ -27,6 +27,7 @@ repos:
args: [--check-untyped-defs]
exclude: ^superset-extensions-cli/
additional_dependencies: [
types-cachetools,
types-simplejson,
types-python-dateutil,
types-requests,

View File

@@ -67,25 +67,15 @@ temporary_superset_ui/*
# skip license checks for auto-generated test snapshots
.*snap
# docs overrides for third party logos we don't have the rights to
google-big-query.svg
google-sheets.svg
ibm-db2.svg
postgresql.svg
snowflake.svg
ydb.svg
loading.svg
apache-solr.svg
azure.svg
superset.svg
# docs third-party logos, i.e. docs/static/img/logos/*
# docs third-party logos (database logos, org logos, etc.)
databases/*
logos/*
# docs-related
erd.puml
erd.svg
intro_header.txt
TODO.md
# for LLMs
llm-context.md

View File

@@ -101,6 +101,30 @@ superset/
- **UPDATING.md**: Add breaking changes here
- **Docstrings**: Required for new functions/classes
## Developer Portal: Storybook-to-MDX Documentation
The Developer Portal auto-generates MDX documentation from Storybook stories. **Stories are the single source of truth.**
### Core Philosophy
- **Fix issues in the STORY, not the generator** - When something doesn't render correctly, update the story file first
- **Generator should be lightweight** - It extracts and passes through data; avoid special cases
- **Stories define everything** - Props, controls, galleries, examples all come from story metadata
### Story Requirements for Docs Generation
- Use `export default { title: '...' }` (inline), not `const meta = ...; export default meta;`
- Name interactive stories `Interactive${ComponentName}` (e.g., `InteractiveButton`)
- Define `args` for default prop values
- Define `argTypes` at the story level (not meta level) with control types and descriptions
- Use `parameters.docs.gallery` for size×style variant grids
- Use `parameters.docs.sampleChildren` for components that need children
- Use `parameters.docs.liveExample` for custom live code blocks
- Use `parameters.docs.staticProps` for complex object props that can't be parsed inline
### Generator Location
- Script: `docs/scripts/generate-superset-components.mjs`
- Wrapper: `docs/src/components/StorybookWrapper.jsx`
- Output: `docs/developer_portal/components/`
## Architecture Patterns
### Security & Features

101
README.md
View File

@@ -89,7 +89,7 @@ Superset provides:
**Craft Beautiful, Dynamic Dashboards**
<kbd><img title="View Dashboards" src="https://superset.apache.org/img/screenshots/slack_dash.jpg"/></kbd><br/>
<kbd><img title="View Dashboards" src="https://superset.apache.org/img/screenshots/dashboard.jpg"/></kbd><br/>
**No-Code Chart Builder**
@@ -107,44 +107,67 @@ Here are some of the major database solutions that are supported:
<!-- SUPPORTED_DATABASES_START -->
<p align="center">
<img src="https://superset.apache.org/img/databases/doris.png" alt="apache-doris" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/apache-drill.png" alt="apache-drill" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/druid.png" alt="apache-druid" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/apache-hive.svg" alt="apache-hive" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/apache-impala.png" alt="apache-impala" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/apache-kylin.png" alt="apache-kylin" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/apache-pinot.svg" alt="apache-pinot" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/amazon-athena.jpg" alt="aws-athena" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/redshift.png" alt="aws-redshift" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/clickhouse.png" alt="clickhouse" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/couchbase.svg" alt="couchbase" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/databend.png" alt="databend" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/databricks.png" alt="databricks" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/denodo.png" alt="denodo" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/dremio.png" alt="dremio" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/exasol.png" alt="exasol" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/firebird.png" alt="firebird" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/firebolt.png" alt="firebolt" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/google-big-query.svg" alt="google-bigquery" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/google-sheets.svg" alt="google-sheets" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/ibm-db2.svg" alt="ibm-db2" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/netezza.png" alt="ibm-netezza" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/mariadb.png" alt="mariadb" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/msql.png" alt="microsoft-sql-server" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/mysql.png" alt="mysql" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/oceanbase.svg" alt="oceanbase" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/oraclelogo.png" alt="oracle" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/postgresql.svg" alt="postgresql" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/presto-og.png" alt="presto" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/sap-hana.png" alt="sap-hana" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/snowflake.svg" alt="snowflake" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/sqlite.png" alt="sqlite" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/starrocks.png" alt="starrocks" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/tdengine.png" alt="tdengine" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/teradata.png" alt="teradata" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/trino.png" alt="trino" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/vertica.png" alt="vertica" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/ydb.svg" alt="ydb" border="0" width="120" height="60" class="database-logo" />
<a href="https://superset.apache.org/docs/databases/supported/amazon-athena" title="Amazon Athena"><img src="docs/static/img/databases/amazon-athena.jpg" alt="Amazon Athena" width="76" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/amazon-dynamodb" title="Amazon DynamoDB"><img src="docs/static/img/databases/aws.png" alt="Amazon DynamoDB" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/amazon-redshift" title="Amazon Redshift"><img src="docs/static/img/databases/redshift.png" alt="Amazon Redshift" width="100" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-doris" title="Apache Doris"><img src="docs/static/img/databases/doris.png" alt="Apache Doris" width="103" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-drill" title="Apache Drill"><img src="docs/static/img/databases/apache-drill.png" alt="Apache Drill" width="81" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-druid" title="Apache Druid"><img src="docs/static/img/databases/druid.png" alt="Apache Druid" width="117" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-hive" title="Apache Hive"><img src="docs/static/img/databases/apache-hive.svg" alt="Apache Hive" width="44" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-impala" title="Apache Impala"><img src="docs/static/img/databases/apache-impala.png" alt="Apache Impala" width="21" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-kylin" title="Apache Kylin"><img src="docs/static/img/databases/apache-kylin.png" alt="Apache Kylin" width="44" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-pinot" title="Apache Pinot"><img src="docs/static/img/databases/apache-pinot.svg" alt="Apache Pinot" width="76" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-solr" title="Apache Solr"><img src="docs/static/img/databases/apache-solr.png" alt="Apache Solr" width="79" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-spark-sql" title="Apache Spark SQL"><img src="docs/static/img/databases/apache-spark.png" alt="Apache Spark SQL" width="75" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/ascend" title="Ascend"><img src="docs/static/img/databases/ascend.webp" alt="Ascend" width="117" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/aurora-mysql-data-api" title="Aurora MySQL (Data API)"><img src="docs/static/img/databases/mysql.png" alt="Aurora MySQL (Data API)" width="77" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/aurora-postgresql-data-api" title="Aurora PostgreSQL (Data API)"><img src="docs/static/img/databases/postgresql.svg" alt="Aurora PostgreSQL (Data API)" width="76" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/azure-data-explorer" title="Azure Data Explorer"><img src="docs/static/img/databases/kusto.png" alt="Azure Data Explorer" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/azure-synapse" title="Azure Synapse"><img src="docs/static/img/databases/azure.svg" alt="Azure Synapse" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/clickhouse" title="ClickHouse"><img src="docs/static/img/databases/clickhouse.png" alt="ClickHouse" width="150" height="37" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/cloudflare-d1" title="Cloudflare D1"><img src="docs/static/img/databases/cloudflare.png" alt="Cloudflare D1" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/cockroachdb" title="CockroachDB"><img src="docs/static/img/databases/cockroachdb.png" alt="CockroachDB" width="150" height="24" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/couchbase" title="Couchbase"><img src="docs/static/img/databases/couchbase.svg" alt="Couchbase" width="150" height="35" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/cratedb" title="CrateDB"><img src="docs/static/img/databases/cratedb.svg" alt="CrateDB" width="180" height="24" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/databend" title="Databend"><img src="docs/static/img/databases/databend.png" alt="Databend" width="100" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/databricks" title="Databricks"><img src="docs/static/img/databases/databricks.png" alt="Databricks" width="152" height="24" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/denodo" title="Denodo"><img src="docs/static/img/databases/denodo.png" alt="Denodo" width="138" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/dremio" title="Dremio"><img src="docs/static/img/databases/dremio.png" alt="Dremio" width="126" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/duckdb" title="DuckDB"><img src="docs/static/img/databases/duckdb.png" alt="DuckDB" width="52" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/elasticsearch" title="Elasticsearch"><img src="docs/static/img/databases/elasticsearch.png" alt="Elasticsearch" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/exasol" title="Exasol"><img src="docs/static/img/databases/exasol.png" alt="Exasol" width="72" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/firebird" title="Firebird"><img src="docs/static/img/databases/firebird.png" alt="Firebird" width="100" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/firebolt" title="Firebolt"><img src="docs/static/img/databases/firebolt.png" alt="Firebolt" width="100" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/google-bigquery" title="Google BigQuery"><img src="docs/static/img/databases/google-big-query.svg" alt="Google BigQuery" width="76" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/google-sheets" title="Google Sheets"><img src="docs/static/img/databases/google-sheets.svg" alt="Google Sheets" width="76" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/greenplum" title="Greenplum"><img src="docs/static/img/databases/greenplum.png" alt="Greenplum" width="124" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/hologres" title="Hologres"><img src="docs/static/img/databases/hologres.png" alt="Hologres" width="44" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/ibm-db2" title="IBM Db2"><img src="docs/static/img/databases/ibm-db2.svg" alt="IBM Db2" width="91" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/ibm-netezza-performance-server" title="IBM Netezza Performance Server"><img src="docs/static/img/databases/netezza.png" alt="IBM Netezza Performance Server" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/mariadb" title="MariaDB"><img src="docs/static/img/databases/mariadb.png" alt="MariaDB" width="150" height="37" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/microsoft-sql-server" title="Microsoft SQL Server"><img src="docs/static/img/databases/msql.png" alt="Microsoft SQL Server" width="50" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/monetdb" title="MonetDB"><img src="docs/static/img/databases/monet-db.png" alt="MonetDB" width="100" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/mongodb" title="MongoDB"><img src="docs/static/img/databases/mongodb.png" alt="MongoDB" width="150" height="38" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/motherduck" title="MotherDuck"><img src="docs/static/img/databases/motherduck.png" alt="MotherDuck" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/oceanbase" title="OceanBase"><img src="docs/static/img/databases/oceanbase.svg" alt="OceanBase" width="175" height="24" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/oracle" title="Oracle"><img src="docs/static/img/databases/oraclelogo.png" alt="Oracle" width="111" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/presto" title="Presto"><img src="docs/static/img/databases/presto-og.png" alt="Presto" width="127" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/risingwave" title="RisingWave"><img src="docs/static/img/databases/risingwave.svg" alt="RisingWave" width="147" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/sap-hana" title="SAP HANA"><img src="docs/static/img/databases/sap-hana.png" alt="SAP HANA" width="137" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/sap-sybase" title="SAP Sybase"><img src="docs/static/img/databases/sybase.png" alt="SAP Sybase" width="100" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/shillelagh" title="Shillelagh"><img src="docs/static/img/databases/shillelagh.png" alt="Shillelagh" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/singlestore" title="SingleStore"><img src="docs/static/img/databases/singlestore.png" alt="SingleStore" width="150" height="31" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/snowflake" title="Snowflake"><img src="docs/static/img/databases/snowflake.svg" alt="Snowflake" width="76" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/sqlite" title="SQLite"><img src="docs/static/img/databases/sqlite.png" alt="SQLite" width="84" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/starrocks" title="StarRocks"><img src="docs/static/img/databases/starrocks.png" alt="StarRocks" width="149" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/superset-meta-database" title="Superset meta database"><img src="docs/static/img/databases/superset.svg" alt="Superset meta database" width="150" height="39" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/tdengine" title="TDengine"><img src="docs/static/img/databases/tdengine.png" alt="TDengine" width="140" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/teradata" title="Teradata"><img src="docs/static/img/databases/teradata.png" alt="Teradata" width="124" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/timescaledb" title="TimescaleDB"><img src="docs/static/img/databases/timescale.png" alt="TimescaleDB" width="150" height="36" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/trino" title="Trino"><img src="docs/static/img/databases/trino.png" alt="Trino" width="89" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/vertica" title="Vertica"><img src="docs/static/img/databases/vertica.png" alt="Vertica" width="128" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/ydb" title="YDB"><img src="docs/static/img/databases/ydb.svg" alt="YDB" width="110" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/yugabytedb" title="YugabyteDB"><img src="docs/static/img/databases/yugabyte.png" alt="YugabyteDB" width="150" height="26" /></a>
</p>
<!-- SUPPORTED_DATABASES_END -->

View File

@@ -136,10 +136,6 @@ categories:
url: https://www.dropit.shop/
contributors: ["@dropit-dev"]
- name: Fanatics
url: https://www.fanatics.com/
contributors: ["@coderfender"]
- name: Fordeal
url: https://www.fordeal.com
contributors: ["@Renkai"]
@@ -291,8 +287,10 @@ categories:
url: https://www.gfk.com/home
contributors: ["@mherr"]
# Logo approved by @anmol-hpe on behalf of HPE
- name: HPE
url: https://www.hpe.com/in/en/home.html
logo: hpe.png
contributors: ["@anmol-hpe"]
- name: Hydrolix
@@ -432,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"]
@@ -622,6 +625,20 @@ categories:
- name: Stockarea
url: https://stockarea.io
Sports:
- name: Club 25 de Agosto (Femenino / Women's Team)
url: https://www.instagram.com/25deagosto.basketfemenino/
contributors: [ "@lion90" ]
logo: club25deagosto.svg
- name: Fanatics
url: https://www.fanatics.com/
contributors: [ "@coderfender" ]
- name: komoot
url: https://www.komoot.com/
contributors: [ "@christophlingg" ]
Others:
- name: 10Web
url: https://10web.io/
@@ -657,10 +674,6 @@ categories:
url: https://www.increff.com/
contributors: ["@ishansinghania"]
- name: komoot
url: https://www.komoot.com/
contributors: ["@christophlingg"]
- name: Let's Roam
url: https://www.letsroam.com/

View File

@@ -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
@@ -163,6 +178,28 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
### Breaking Changes
- [37370](https://github.com/apache/superset/pull/37370): The `APP_NAME` configuration variable no longer controls the browser window/tab title or other frontend branding. Application names should now be configured using the theme system with the `brandAppName` token. The `APP_NAME` config is still used for backend contexts (MCP service, logs, etc.) and serves as a fallback if `brandAppName` is not set.
- **Migration:**
```python
# Before (Superset 5.x)
APP_NAME = "My Custom App"
# After (Superset 6.x) - Option 1: Use theme system (recommended)
THEME_DEFAULT = {
"token": {
"brandAppName": "My Custom App", # Window titles
"brandLogoAlt": "My Custom App", # Logo alt text
"brandLogoUrl": "/static/assets/images/custom_logo.png"
}
}
# After (Superset 6.x) - Option 2: Temporary fallback
# Keep APP_NAME for now (will be used as fallback for brandAppName)
APP_NAME = "My Custom App"
# But you should migrate to THEME_DEFAULT.token.brandAppName
```
- **Note:** For dark mode, set the same tokens in `THEME_DARK` configuration.
- [36317](https://github.com/apache/superset/pull/36317): The `CUSTOM_FONT_URLS` configuration option has been removed. Use the new per-theme `fontUrls` token in `THEME_DEFAULT` or database-managed themes instead.
- **Before:**
```python
@@ -177,7 +214,7 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
"fontUrls": [
"https://fonts.example.com/myfont.css",
],
# ... other tokens
# ... other tokens
}
}
```

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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.

View File

@@ -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"
}

View File

@@ -0,0 +1,115 @@
# Developer Portal Documentation Instructions
## Core Principle: Stories Are the Single Source of Truth
When working on the Storybook-to-MDX documentation system:
**ALWAYS fix the story first. NEVER add workarounds to the generator.**
## Why This Matters
The generator (`scripts/generate-superset-components.mjs`) should be lightweight - it extracts data from stories and passes it through. When you add special cases to the generator:
- It becomes harder to maintain
- Stories diverge from their docs representation
- Future stories need to know about generator quirks
When you fix stories to match the expected patterns:
- Stories work identically in Storybook and Docs
- The generator stays simple and predictable
- Patterns are consistent and learnable
## Story Patterns for Docs Generation
### Required Structure
```tsx
// Use inline export default (NOT const meta = ...; export default meta)
export default {
title: 'Components/MyComponent',
component: MyComponent,
};
// Name interactive stories with Interactive prefix
export const InteractiveMyComponent: Story = {
args: {
// Default prop values
},
argTypes: {
// Control definitions - MUST be at story level, not meta level
propName: {
control: { type: 'select' },
options: ['a', 'b', 'c'],
description: 'What this prop does',
},
},
};
```
### For Components with Variants (size × style grids)
```tsx
const sizes = ['small', 'medium', 'large'];
const variants = ['primary', 'secondary', 'danger'];
InteractiveButton.parameters = {
docs: {
gallery: {
component: 'Button',
sizes,
styles: variants,
sizeProp: 'size',
styleProp: 'variant',
},
},
};
```
### For Components Requiring Children
```tsx
InteractiveIconTooltip.parameters = {
docs: {
// Component descriptors with dot notation for nested components
sampleChildren: [{ component: 'Icons.InfoCircleOutlined', props: { iconSize: 'l' } }],
},
};
```
### For Custom Live Code Examples
```tsx
InteractiveMyComponent.parameters = {
docs: {
liveExample: `function Demo() {
return <MyComponent prop="value">Content</MyComponent>;
}`,
},
};
```
### For Complex Props (objects, arrays)
```tsx
InteractiveMenu.parameters = {
docs: {
staticProps: {
items: [
{ key: '1', label: 'Item 1' },
{ key: '2', label: 'Item 2' },
],
},
},
};
```
## Common Issues and How to Fix Them (in the Story)
| Issue | Wrong Approach | Right Approach |
|-------|---------------|----------------|
| Component not generated | Add pattern to generator | Change story to use inline `export default` |
| Control shows as text instead of select | Add special case in generator | Add `argTypes` with `control: { type: 'select' }` |
| Missing children/content | Modify StorybookWrapper | Add `parameters.docs.sampleChildren` |
| Gallery not showing | Add to generator output | Add `parameters.docs.gallery` config |
| Wrong live example | Hardcode in generator | Add `parameters.docs.liveExample` |
## Files
- **Generator**: `docs/scripts/generate-superset-components.mjs`
- **Wrapper**: `docs/src/components/StorybookWrapper.jsx`
- **Output**: `docs/developer_portal/components/`
- **Stories**: `superset-frontend/packages/superset-ui-core/src/components/*/`

11
docs/.gitignore vendored
View File

@@ -31,5 +31,16 @@ static/badges/
# Source of truth is in superset/db_engine_specs/*.py metadata attributes
docs/databases/
# Generated API documentation (regenerated at build time from openapi.json)
# Source of truth is static/resources/openapi.json
docs/api/
# Generated component documentation MDX files (regenerated at build time)
# Source of truth is Storybook stories in superset-frontend/packages/superset-ui-core/src/components/
developer_portal/components/
# Generated extension component documentation (regenerated at build time)
developer_portal/extensions/components/
# Note: src/data/databases.json is COMMITTED (not ignored) to preserve feature diagnostics
# that require Flask context to generate. Update it locally with: npm run gen-db-docs

View File

@@ -19,5 +19,14 @@
*/
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
presets: [
[
require.resolve('@docusaurus/core/lib/babel/preset'),
{
runtime: 'automatic',
importSource: '@emotion/react',
},
],
],
plugins: ['@emotion/babel-plugin'],
};

View File

@@ -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

View File

@@ -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
@@ -258,19 +258,7 @@ For debugging the Flask backend:
### Storybook
Storybook is used for developing and testing UI components in isolation:
```bash
cd superset-frontend
# Start Storybook
npm run storybook
# Build static Storybook
npm run build-storybook
```
Access Storybook at http://localhost:6006
See the dedicated [Storybook documentation](../testing/storybook) for information on running Storybook locally and adding new stories.
## Contributing Translations

View File

@@ -1,248 +0,0 @@
---
title: Administrator Configuration
sidebar_position: 12
---
<!--
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.
-->
# Extension Administrator Configuration
This guide covers how to configure extension security for production deployments. As an administrator, you control which extensions can run and at what trust level.
## Trust Configuration
Configure extension trust in `superset_config.py`:
```python
EXTENSIONS_TRUST_CONFIG = {
# Extensions that can run with full privileges ('core' trust level)
"trusted_extensions": [
"official-parquet-export",
"enterprise-sso-plugin",
],
# Allow any extension to run as 'core' without signature verification
# WARNING: NEVER enable in production - development use only!
"allow_unsigned_core": False,
# Default sandbox for extensions without explicit trust configuration
# Options: 'core', 'iframe', 'worker', 'wasm'
"default_trust_level": "iframe",
# Require valid signatures for extensions requesting 'core' trust
# Recommended for production deployments
"require_core_signatures": True,
# Public keys for verified publishers (file paths or PEM strings)
"trusted_signers": [
"/etc/superset/keys/apache-official.pub",
"/etc/superset/keys/enterprise-team.pub",
],
}
```
## Configuration Options
### `trusted_extensions`
A list of extension IDs that are allowed to run as `core` trust level without signature verification. Use this for extensions you've reviewed and trust completely.
```python
"trusted_extensions": [
"my-company-plugin",
"approved-community-extension",
],
```
### `allow_unsigned_core`
When `True`, allows any extension to run as `core` trust level regardless of signatures or trusted list. **Never enable this in production** - it's intended only for development environments.
```python
# Development only!
"allow_unsigned_core": True,
```
### `default_trust_level`
The trust level assigned to extensions that don't specify one in their manifest. The safest option is `iframe`, which provides browser-enforced isolation.
| Level | Description |
|-------|-------------|
| `iframe` | Browser-sandboxed iframe with controlled API access (recommended default) |
| `worker` | Web Worker sandbox for command-only extensions |
| `wasm` | WASM sandbox with no DOM access (most restrictive) |
| `core` | Full access to main context (not recommended as default) |
```python
"default_trust_level": "iframe",
```
### `require_core_signatures`
When `True`, extensions requesting `core` trust level must have a valid signature from a trusted signer. Extensions without valid signatures are downgraded to `default_trust_level`.
```python
"require_core_signatures": True,
```
### `trusted_signers`
A list of public keys authorized to sign extensions. Keys can be specified as file paths or inline PEM strings.
```python
"trusted_signers": [
# File path to public key
"/etc/superset/keys/publisher.pub",
# Inline PEM string
"""-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA...
-----END PUBLIC KEY-----""",
],
```
## Signature Verification
### How It Works
1. Extension developers generate a signing keypair using the CLI
2. They sign their extension's manifest during the build process
3. The signed bundle includes `manifest.sig` alongside `manifest.json`
4. When Superset loads the extension, it verifies the signature against `trusted_signers`
5. If verification passes, the extension can run at its requested trust level
### Configuring Trusted Signers
1. Obtain the publisher's public key file (`.pub` extension)
2. Place it in a secure location on your server (e.g., `/etc/superset/keys/`)
3. Add the path to `trusted_signers` in your configuration
```python
EXTENSIONS_TRUST_CONFIG = {
"trusted_signers": [
"/etc/superset/keys/acme-corp.pub",
],
"require_core_signatures": True,
}
```
### Verifying a Key Fingerprint
Before adding a public key to your trusted signers, verify its fingerprint with the publisher:
```bash
# On the publisher's machine
superset-extensions generate-keys --output my-key.pem
# Output: Fingerprint: MCowBQYDK2Vw...
```
Compare this fingerprint with what you receive to ensure authenticity.
## Security Recommendations
### Production Deployments
1. **Set `require_core_signatures: True`** - Ensures core extensions are verified
2. **Set `allow_unsigned_core: False`** - Never allow unsigned core extensions
3. **Use `iframe` as default** - Provides strong browser isolation
4. **Limit `trusted_extensions`** - Only add extensions you've thoroughly reviewed
5. **Secure key storage** - Store public keys in protected directories
### Development Environments
For local development, you may relax some restrictions:
```python
# Development configuration
EXTENSIONS_TRUST_CONFIG = {
"trusted_extensions": [],
"allow_unsigned_core": True, # OK for development
"default_trust_level": "core", # Easier debugging
"require_core_signatures": False,
"trusted_signers": [],
}
```
## Extension Installation
### From Trusted Sources
1. Download the `.supx` bundle from a trusted source
2. Verify any checksums or signatures provided by the publisher
3. Place the bundle in your `EXTENSIONS_PATH` directory
4. If the extension requires `core` trust, add it to `trusted_extensions` or configure signature verification
### From Community Registry
Extensions from the community registry should be treated as semi-trusted at best. Consider:
1. Using `iframe` sandbox for community extensions
2. Reviewing the extension's source code before installation
3. Testing in a staging environment first
## Monitoring Extensions
### Logging
Extension trust decisions are logged at the INFO level:
```
INFO: Extension my-extension granted core trust (trusted + valid signature)
WARNING: Extension unknown-ext trust downgraded from core to iframe: Extension not in trusted list
```
Review these logs to monitor extension behavior and identify potential issues.
### Trust Downgrades
If an extension's trust is downgraded, you'll see a warning in the logs. Common reasons:
| Reason | Meaning |
|--------|---------|
| "Extension not in trusted list" | Extension requests core but isn't in `trusted_extensions` |
| "Core trust requires a valid signature" | `require_core_signatures` is enabled but signature is missing |
| "Signature verification failed" | Signature doesn't match any trusted signer |
## Troubleshooting
### Extension Not Loading as Core
1. Check if the extension ID is in `trusted_extensions`
2. If using signatures, verify the public key is in `trusted_signers`
3. Check logs for trust downgrade messages
4. Verify the extension bundle contains `manifest.sig`
### Signature Verification Failing
1. Ensure the public key file is readable by the Superset process
2. Verify the key is in PEM format with correct Ed25519 type
3. Check that the manifest wasn't modified after signing
4. Confirm the signature was created with the matching private key
### Permission Denied Errors
Sandboxed extensions may encounter permission errors if:
1. The extension's declared permissions don't match its API calls
2. The sandbox is blocking access correctly (working as intended)
3. The extension was downgraded to a more restrictive sandbox
Check the extension's `sandbox.permissions` configuration against its actual needs.

View File

@@ -1,131 +0,0 @@
---
title: Alert
sidebar_label: Alert
---
<!--
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
import { Alert } from '@apache-superset/core/ui';
# Alert
Alert component for displaying important messages to users. Wraps Ant Design Alert with sensible defaults and improved accessibility.
## Live Example
<StoryWithControls
component={Alert}
props={{
closable: true,
type: 'info',
message: 'This is a sample alert message.',
description: 'Sample description for additional context.',
showIcon: true
}}
controls={[
{
name: 'type',
label: 'Type',
type: 'select',
options: [
'info',
'error',
'warning',
'success'
]
},
{
name: 'closable',
label: 'Closable',
type: 'boolean'
},
{
name: 'showIcon',
label: 'Show Icon',
type: 'boolean'
},
{
name: 'message',
label: 'Message',
type: 'text'
},
{
name: 'description',
label: 'Description',
type: 'text'
}
]}
/>
## Try It
Edit the code below to experiment with the component:
```tsx live
function Demo() {
return (
<Alert
closable
type="info"
message="This is a sample alert message."
description="Sample description for additional context."
showIcon
/>
);
}
```
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `closable` | `boolean` | `true` | Whether the Alert can be closed with a close button. |
| `type` | `string` | `"info"` | Type of the alert (e.g., info, error, warning, success). |
| `message` | `string` | `"This is a sample alert message."` | Message |
| `description` | `string` | `"Sample description for additional context."` | Description |
| `showIcon` | `boolean` | `true` | Whether to display an icon in the Alert. |
## Usage in Extensions
This component is available in the `@apache-superset/core/ui` package, which is automatically available to Superset extensions.
```tsx
import { Alert } from '@apache-superset/core/ui';
function MyExtension() {
return (
<Alert
closable
type="info"
message="This is a sample alert message."
/>
);
}
```
## Source Links
- [Story file](https://github.com/apache/superset/blob/master/superset-frontend/packages/superset-core/src/ui/components/Alert/Alert.stories.tsx)
- [Component source](https://github.com/apache/superset/blob/master/superset-frontend/packages/superset-core/src/ui/components/Alert/index.tsx)
---
*This page was auto-generated from the component's Storybook story.*

View File

@@ -1,93 +0,0 @@
---
title: Extension Components
sidebar_label: Overview
sidebar_position: 1
---
<!--
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.
-->
# Extension Components
These UI components are available to Superset extension developers through the `@apache-superset/core/ui` package. They provide a consistent look and feel with the rest of Superset and are designed to be used in extension panels, views, and other UI elements.
## Available Components
- [Alert](./alert)
## Usage
All components are exported from the `@apache-superset/core/ui` package:
```tsx
import { Alert } from '@apache-superset/core/ui';
export function MyExtensionPanel() {
return (
<Alert type="info">
Welcome to my extension!
</Alert>
);
}
```
## Adding New Components
Components in `@apache-superset/core/ui` are automatically documented here. To add a new extension component:
1. Add the component to `superset-frontend/packages/superset-core/src/ui/components/`
2. Export it from `superset-frontend/packages/superset-core/src/ui/components/index.ts`
3. Create a Storybook story with an `Interactive` export:
```tsx
export default {
title: 'Extension Components/MyComponent',
component: MyComponent,
parameters: {
docs: {
description: {
component: 'Description of the component...',
},
},
},
};
export const InteractiveMyComponent = (args) => <MyComponent {...args} />;
InteractiveMyComponent.args = {
variant: 'primary',
disabled: false,
};
InteractiveMyComponent.argTypes = {
variant: {
control: { type: 'select' },
options: ['primary', 'secondary'],
},
disabled: {
control: { type: 'boolean' },
},
};
```
4. Run `yarn start` in `docs/` - the page generates automatically!
## Interactive Documentation
For interactive examples with controls, visit the [Storybook](/storybook/?path=/docs/extension-components--docs).

View File

@@ -34,7 +34,7 @@ Frontend contribution types allow extensions to extend Superset's user interface
Extensions can add new views or panels to the host application, such as custom SQL Lab panels, dashboards, or other UI components. Each view is registered with a unique ID and can be activated or deactivated as needed. Contribution areas are uniquely identified (e.g., `sqllab.panels` for SQL Lab panels), enabling seamless integration into specific parts of the application.
``` json
```json
"frontend": {
"contributions": {
"views": {
@@ -53,7 +53,7 @@ Extensions can add new views or panels to the host application, such as custom S
Extensions can define custom commands that can be executed within the host application, such as context-aware actions or menu options. Each command can specify properties like a unique command identifier, an icon, a title, and a description. These commands can be invoked by users through menus, keyboard shortcuts, or other UI elements, enabling extensions to add rich, interactive functionality to Superset.
``` json
```json
"frontend": {
"contributions": {
"commands": [
@@ -72,7 +72,7 @@ Extensions can define custom commands that can be executed within the host appli
Extensions can contribute new menu items or context menus to the host application, providing users with additional actions and options. Each menu item can specify properties such as the target view, the command to execute, its placement (primary, secondary, or context), and conditions for when it should be displayed. Menu contribution areas are uniquely identified (e.g., `sqllab.editor` for the SQL Lab editor), allowing extensions to seamlessly integrate their functionality into specific menus and workflows within Superset.
``` json
```json
"frontend": {
"contributions": {
"menus": {
@@ -101,6 +101,27 @@ Extensions can contribute new menu items or context menus to the host applicatio
}
```
### Editors
Extensions can replace Superset's default text editors with custom implementations. This enables enhanced editing experiences using alternative editor frameworks like Monaco, CodeMirror, or custom solutions. When an extension registers an editor for a language, it replaces the default Ace editor in all locations that use that language (SQL Lab, Dashboard Properties, CSS editors, etc.).
```json
"frontend": {
"contributions": {
"editors": [
{
"id": "my_extension.monaco_sql",
"name": "Monaco SQL Editor",
"languages": ["sql"],
"description": "Monaco-based SQL editor with IntelliSense"
}
]
}
}
```
See [Editors Extension Point](./extension-points/editors) for implementation details.
## Backend
Backend contribution types allow extensions to extend Superset's server-side capabilities with new API endpoints, MCP tools, and MCP prompts.
@@ -109,7 +130,7 @@ Backend contribution types allow extensions to extend Superset's server-side cap
Extensions can register custom REST API endpoints under the `/api/v1/extensions/` namespace. This dedicated namespace prevents conflicts with built-in endpoints and provides a clear separation between core and extension functionality.
``` json
```json
"backend": {
"entryPoints": ["my_extension.entrypoint"],
"files": ["backend/src/my_extension/**/*.py"]
@@ -118,7 +139,7 @@ Extensions can register custom REST API endpoints under the `/api/v1/extensions/
The entry point module registers the API with Superset:
``` python
```python
from superset_core.api.rest_api import add_extension_api
from .api import MyExtensionAPI

View File

@@ -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

View File

@@ -0,0 +1,245 @@
---
title: Editors
sidebar_position: 2
---
<!--
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.
-->
# Editor Contributions
Extensions can replace Superset's default text editors with custom implementations. This allows you to provide enhanced editing experiences using alternative editor frameworks like Monaco, CodeMirror, or custom solutions.
## Overview
Superset uses text editors in various places throughout the application:
| Language | Locations |
|----------|-----------|
| `sql` | SQL Lab, Metric/Filter Popovers |
| `json` | Dashboard Properties, Annotation Modal, Theme Modal |
| `css` | Dashboard Properties, CSS Template Modal |
| `markdown` | Dashboard Markdown component |
| `yaml` | Template Params Editor |
By registering an editor provider for a language, your extension replaces the default Ace editor in **all** locations that use that language.
## Manifest Configuration
Declare editor contributions in your `extension.json` manifest:
```json
{
"name": "monaco-editor",
"version": "1.0.0",
"frontend": {
"contributions": {
"editors": [
{
"id": "monaco-editor.sql",
"name": "Monaco SQL Editor",
"languages": ["sql"],
"description": "Monaco-based SQL editor with IntelliSense"
}
]
}
}
}
```
## Implementing an Editor
Your editor component must implement the `EditorProps` interface and expose an `EditorHandle` via `forwardRef`. For the complete interface definitions, see `@apache-superset/core/api/editors.ts`.
### Key EditorProps
```typescript
interface EditorProps {
/** Controlled value */
value: string;
/** Content change handler */
onChange: (value: string) => void;
/** Language mode for syntax highlighting */
language: EditorLanguage;
/** Keyboard shortcuts to register */
hotkeys?: EditorHotkey[];
/** Callback when editor is ready with imperative handle */
onReady?: (handle: EditorHandle) => void;
/** Host-specific context (e.g., database info from SQL Lab) */
metadata?: Record<string, unknown>;
// ... additional props for styling, annotations, etc.
}
```
### Key EditorHandle Methods
```typescript
interface EditorHandle {
/** Focus the editor */
focus(): void;
/** Get the current editor content */
getValue(): string;
/** Get the current cursor position */
getCursorPosition(): Position;
/** Move the cursor to a specific position */
moveCursorToPosition(position: Position): void;
/** Set the selection range */
setSelection(selection: Range): void;
/** Scroll to a specific line */
scrollToLine(line: number): void;
// ... additional methods for text manipulation, annotations, etc.
}
```
## Example Implementation
Here's an example of a Monaco-based SQL editor implementing the key interfaces shown above:
### MonacoSQLEditor.tsx
```typescript
import { forwardRef, useRef, useImperativeHandle, useEffect } from 'react';
import * as monaco from 'monaco-editor';
import type { editors } from '@apache-superset/core';
const MonacoSQLEditor = forwardRef<editors.EditorHandle, editors.EditorProps>(
(props, ref) => {
const { value, onChange, hotkeys, onReady } = props;
const containerRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
// Implement EditorHandle interface
const handle: editors.EditorHandle = {
focus: () => editorRef.current?.focus(),
getValue: () => editorRef.current?.getValue() ?? '',
getCursorPosition: () => {
const pos = editorRef.current?.getPosition();
return { line: (pos?.lineNumber ?? 1) - 1, column: (pos?.column ?? 1) - 1 };
},
// ... implement remaining methods
};
useImperativeHandle(ref, () => handle, []);
useEffect(() => {
if (!containerRef.current) return;
const editor = monaco.editor.create(containerRef.current, { value, language: 'sql' });
editorRef.current = editor;
editor.onDidChangeModelContent(() => onChange(editor.getValue()));
// Register hotkeys
hotkeys?.forEach(hotkey => {
editor.addAction({
id: hotkey.name,
label: hotkey.name,
run: () => hotkey.exec(handle),
});
});
onReady?.(handle);
return () => editor.dispose();
}, []);
return <div ref={containerRef} style={{ height: '100%', width: '100%' }} />;
},
);
export default MonacoSQLEditor;
```
### activate.ts
```typescript
import { editors } from '@apache-superset/core';
import MonacoSQLEditor from './MonacoSQLEditor';
export function activate(context) {
// Register the Monaco editor for SQL
const disposable = editors.registerEditorProvider(
{
id: 'monaco-sql-editor.sql',
name: 'Monaco SQL Editor',
languages: ['sql'],
},
MonacoSQLEditor,
);
context.subscriptions.push(disposable);
}
```
## Handling Hotkeys
Superset passes keyboard shortcuts via the `hotkeys` prop. Each hotkey includes an `exec` function that receives the `EditorHandle`:
```typescript
interface EditorHotkey {
name: string;
key: string; // e.g., "Ctrl-Enter", "Alt-Shift-F"
description?: string;
exec: (handle: EditorHandle) => void;
}
```
Your editor must register these hotkeys with your editor framework and call `exec(handle)` when triggered.
## Keywords
Superset passes static autocomplete suggestions via the `keywords` prop. These include table names, column names, and SQL functions based on the current database context:
```typescript
interface EditorKeyword {
name: string;
value?: string; // Text to insert (defaults to name)
meta?: string; // Category like "table", "column", "function"
score?: number; // Sorting priority
}
```
Your editor should convert these to your framework's completion format and register them for autocomplete.
## Completion Providers
For dynamic autocomplete (e.g., fetching suggestions as the user types), implement and register a `CompletionProvider` via the `EditorHandle`:
```typescript
const provider: CompletionProvider = {
id: 'my-sql-completions',
triggerCharacters: ['.', ' '],
provideCompletions: async (content, position, context) => {
// Use context.metadata for database info
// Return array of CompletionItem
return [
{ label: 'SELECT', insertText: 'SELECT', kind: 'keyword' },
// ...
];
},
};
// Register during editor initialization
const disposable = handle.registerCompletionProvider(provider);
```
## Next Steps
- **[SQL Lab Extension Points](./sqllab)** - Learn about other SQL Lab customizations
- **[Contribution Types](../contribution-types)** - Explore other contribution types
- **[Development](../development)** - Set up your development environment

View File

@@ -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);
}

View File

@@ -38,6 +38,7 @@ This page serves as a registry of community-created Superset extensions. These e
| [SQL Lab Result Stats](https://github.com/michael-s-molina/superset-extensions/tree/main/result_stats) | A SQL Lab extension that automatically computes statistics for query results, providing type-aware analysis including numeric metrics (min, max, mean, median, std dev), string analysis (length, empty counts), and date range information. | Michael S. Molina | <a href="/img/extensions/result-stats.png" target="_blank"><img src="/img/extensions/result-stats.png" alt="Result Stats" width="120" /></a> |
| [SQL Snippets](https://github.com/michael-s-molina/superset-extensions/tree/main/sql_snippets) | A SQL Lab extension that provides reusable SQL code snippets, enabling quick insertion of commonly used code blocks such as license headers, author information, and frequently used SQL patterns. | Michael S. Molina | <a href="/img/extensions/sql-snippets.png" target="_blank"><img src="/img/extensions/sql-snippets.png" alt="SQL Snippets" width="120" /></a> |
| [SQL Lab Query Estimator](https://github.com/michael-s-molina/superset-extensions/tree/main/query_estimator) | A SQL Lab panel that analyzes query execution plans to estimate resource impact, detect performance issues like Cartesian products and high-cost operations, and visualize the query plan tree. | Michael S. Molina | <a href="/img/extensions/query-estimator.png" target="_blank"><img src="/img/extensions/query-estimator.png" alt="Query Estimator" width="120" /></a> |
| [Editors Bundle](https://github.com/michael-s-molina/superset-extensions/tree/main/editors_bundle) | A Superset extension that demonstrates how to provide custom code editors for different languages. This extension showcases the editor contribution system by registering alternative editors that can replace Superset's default Ace editor. | Michael S. Molina | <a href="/img/extensions/editors-bundle.png" target="_blank"><img src="/img/extensions/editors-bundle.png" alt="Editors Bundle" width="120" /></a> |
## How to Add Your Extension

View File

@@ -1,416 +0,0 @@
---
title: Extension Sandboxing
sidebar_position: 10
---
<!--
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.
-->
# Extension Sandboxing
Superset provides a tiered sandbox architecture for running extensions with varying levels of trust and isolation. This system balances security with functionality, allowing extensions to be safely executed based on their trust level and requirements.
## Overview
The sandbox system supports three tiers of trust:
| Tier | Trust Level | Isolation | Use Case |
|------|-------------|-----------|----------|
| **Tier 1** | `core` | None (main context) | Official/signed extensions |
| **Tier 2** | `iframe` | Browser sandbox | Community UI extensions |
| **Tier 3** | `wasm` | WASM sandbox | Logic-only extensions |
## Trust Levels
### Tier 1: Core (Trusted)
Core extensions run in the main JavaScript context with full access to Superset APIs, DOM, and browser capabilities. This is the same behavior as legacy extensions.
**Requirements:**
- Must be in the trusted extensions list, OR
- `allowUnsignedCore` configuration must be enabled
**Use cases:**
- Official Apache Superset extensions
- Enterprise-verified plugins
- Extensions from trusted sources
```json
{
"id": "official-extension",
"sandbox": {
"trustLevel": "core",
"requiresSignature": true
}
}
```
### Tier 2: Iframe (Semi-Trusted)
Iframe-sandboxed extensions run in isolated browser sandboxes with controlled API access via postMessage. This provides strong browser-enforced isolation while still allowing full UI rendering.
**Security features:**
- Browser-enforced same-origin isolation
- Content Security Policy (CSP) restrictions
- Permission-based API access
- No access to parent window's cookies, localStorage, or DOM
**Use cases:**
- Community-contributed extensions
- Third-party plugins
- Extensions that render custom UI
```json
{
"id": "community-extension",
"sandbox": {
"trustLevel": "iframe",
"permissions": ["sqllab:read", "notification:show"],
"csp": {
"connectSrc": ["https://api.example.com"]
}
}
}
```
### Tier 3: WASM (Untrusted)
WASM-sandboxed extensions run in a QuickJS WebAssembly sandbox with no DOM access. Only explicitly injected APIs are available. This provides the highest level of isolation.
**Security features:**
- Complete isolation from browser APIs
- Memory limits to prevent DoS
- Execution time limits
- No network or DOM access
**Use cases:**
- Custom data transformations
- Calculated fields and formatters
- Data validation rules
- Custom aggregation functions
```json
{
"id": "formatter-extension",
"sandbox": {
"trustLevel": "wasm",
"resourceLimits": {
"maxMemory": 10485760,
"maxExecutionTime": 5000
}
}
}
```
## Permissions
Sandboxed extensions (Tier 2 and 3) must declare the permissions they need. Permissions follow a least-privilege model.
### Available Permissions
| Permission | Description |
|------------|-------------|
| `api:read` | Read-only access to Superset APIs |
| `api:write` | Write access to Superset APIs |
| `sqllab:read` | Read SQL Lab state (queries, results) |
| `sqllab:execute` | Execute SQL queries |
| `dashboard:read` | Read dashboard data |
| `dashboard:write` | Modify dashboards |
| `chart:read` | Read chart data |
| `chart:write` | Modify charts |
| `user:read` | Read current user info |
| `notification:show` | Show notifications to user |
| `modal:open` | Open modal dialogs |
| `navigation:redirect` | Navigate to other pages |
| `clipboard:write` | Write to clipboard |
| `download:file` | Trigger file downloads |
### Example Permission Declaration
```json
{
"sandbox": {
"trustLevel": "iframe",
"permissions": [
"sqllab:read",
"notification:show",
"download:file"
]
}
}
```
## Sandboxed Extension API
Extensions running in iframe sandboxes have access to a controlled API through the `window.superset` object.
### SQL Lab API
```typescript
// Get the current SQL Lab tab (requires sqllab:read)
const tab = await window.superset.sqlLab.getCurrentTab();
// Get query results (requires sqllab:read)
const results = await window.superset.sqlLab.getQueryResults(queryId);
```
### Dashboard API
```typescript
// Get dashboard context (requires dashboard:read)
const context = await window.superset.dashboard.getContext();
// Get dashboard filters (requires dashboard:read)
const filters = await window.superset.dashboard.getFilters();
```
### Chart API
```typescript
// Get chart data (requires chart:read)
const chartData = await window.superset.chart.getData(chartId);
```
### User API
```typescript
// Get current user (requires user:read)
const user = await window.superset.user.getCurrentUser();
```
### UI API
```typescript
// Show notification (requires notification:show)
window.superset.ui.showNotification('Success!', 'success');
// Open modal (requires modal:open)
const result = await window.superset.ui.openModal({
title: 'Confirm',
content: 'Are you sure?',
type: 'confirm'
});
// Navigate (requires navigation:redirect)
window.superset.ui.navigateTo('/dashboard/1');
```
### Utility API
```typescript
// Copy to clipboard (requires clipboard:write)
await window.superset.utils.copyToClipboard('text');
// Download file (requires download:file)
window.superset.utils.downloadFile(blob, 'filename.csv');
// Get CSRF token (no permission required)
const token = await window.superset.utils.getCSRFToken();
```
### Event Subscriptions
```typescript
// Subscribe to events
const unsubscribe = window.superset.on('dashboard:filterChange', (filters) => {
console.log('Filters changed:', filters);
});
// Later, unsubscribe
unsubscribe();
```
## Content Security Policy
Iframe-sandboxed extensions can customize their Content Security Policy through the `csp` configuration:
```json
{
"sandbox": {
"trustLevel": "iframe",
"csp": {
"defaultSrc": ["'none'"],
"scriptSrc": ["'unsafe-inline'"],
"styleSrc": ["'unsafe-inline'"],
"imgSrc": ["data:", "blob:", "https://cdn.example.com"],
"connectSrc": ["https://api.example.com"],
"fontSrc": ["data:"]
}
}
}
```
### Default CSP
By default, iframe sandboxes use a restrictive CSP:
```
default-src 'none';
script-src 'unsafe-inline';
style-src 'unsafe-inline';
img-src data: blob:;
font-src data:;
connect-src 'none';
frame-src 'none';
```
## WASM Resource Limits
WASM-sandboxed extensions can configure resource limits:
```json
{
"sandbox": {
"trustLevel": "wasm",
"resourceLimits": {
"maxMemory": 10485760, // 10MB max memory
"maxExecutionTime": 5000, // 5 second timeout
"maxStackSize": 1000 // Max call stack depth
}
}
}
```
### Defaults
- **maxMemory**: 10MB
- **maxExecutionTime**: 5000ms (5 seconds)
- **maxStackSize**: 1000 calls
## Migration Guide
### Migrating from Legacy Extensions
Existing extensions that don't specify a `sandbox` configuration will continue to run as `core` extensions for backward compatibility. To migrate to a sandboxed model:
1. **Assess your extension's requirements**:
- Does it need to render UI? Use `iframe`
- Is it logic-only (formatters, validators)? Use `wasm`
- Does it need full access? Keep as `core` (requires trust)
2. **Add sandbox configuration to extension.json**:
```json
{
"sandbox": {
"trustLevel": "iframe",
"permissions": ["sqllab:read"]
}
}
```
3. **Update your code to use the sandboxed API**:
Before (core extension):
```typescript
import { sqlLab } from '@apache-superset/core';
const tab = sqlLab.getCurrentTab();
```
After (sandboxed extension):
```typescript
const tab = await window.superset.sqlLab.getCurrentTab();
```
4. **Test thoroughly** to ensure all functionality works within the sandbox
## Security Comparison
| Aspect | Core | Iframe | WASM |
|--------|------|--------|------|
| DOM Access | Full | Own iframe only | None |
| Network | Full | Restricted (CSP) | None |
| Cookies | Full | None | None |
| localStorage | Full | None | None |
| Superset APIs | Full | Controlled bridge | Injected only |
| Performance | Native | Near-native | ~40% slower |
| React rendering | Full | Own instance | Via descriptors |
## Administrator Configuration
Administrators can configure trust settings for their Superset deployment:
```python
# In superset_config.py
EXTENSIONS_TRUST_CONFIG = {
# Extensions allowed to run as 'core'
"trusted_extensions": [
"official-extension-1",
"enterprise-plugin",
],
# Allow unsigned extensions to run as core (not recommended for production)
"allow_unsigned_core": False,
# Default trust level for extensions without sandbox config
"default_trust_level": "iframe",
}
```
## Best Practices
1. **Request minimal permissions** - Only request the permissions your extension actually needs
2. **Prefer iframe over core** - Unless your extension requires deep integration, use iframe sandboxing
3. **Use WASM for pure logic** - If your extension doesn't need UI, WASM provides the best isolation
4. **Handle permission denials gracefully** - Your extension should degrade gracefully if a permission is not granted
5. **Don't store sensitive data** - Sandboxed extensions should not store sensitive user data
6. **Test in sandboxed mode** - Always test your extension in its intended sandbox environment
## Troubleshooting
### Permission Denied Errors
If you see "Permission denied" errors, verify that:
1. The permission is declared in your extension.json
2. The permission was granted by the administrator
3. You're calling the correct API method for that permission
### Timeout Errors (WASM)
If your WASM extension times out:
1. Optimize your code for faster execution
2. Request a higher `maxExecutionTime` limit
3. Break large operations into smaller chunks
### CSP Violations (Iframe)
If resources fail to load due to CSP:
1. Add the required domains to your CSP configuration
2. Ensure you're using HTTPS for external resources
3. Avoid inline scripts and styles where possible
### Core Trust Denied
If your extension is downgraded from `core` to another trust level:
1. Check if the extension ID is in the administrator's `trusted_extensions` list
2. If signature verification is required, ensure the extension is signed
3. Verify the signing key is in the administrator's `trusted_signers`
See [Extension Signing](./signing) for how to sign your extension.
## Related Documentation
- [Security Overview](./security) - Extension security fundamentals
- [Extension Signing](./signing) - How to sign extensions for core trust
- [Administrator Configuration](./admin-configuration) - Trust configuration for admins

View File

@@ -26,44 +26,9 @@ under the License.
By default, extensions are disabled and must be explicitly enabled by setting the `ENABLE_EXTENSIONS` feature flag. Built-in extensions are included as part of the Superset codebase and are held to the same security standards and review processes as the rest of the application.
## Extension Sandboxing
For external extensions, administrators are responsible for evaluating and verifying the security of any extensions they choose to install, just as they would when installing third-party NPM or PyPI packages. At this stage, all extensions run in the same context as the host application, without additional sandboxing. This means that external extensions can impact the security and performance of a Superset environment in the same way as any other installed dependency.
Superset provides a tiered sandbox architecture for running extensions with varying levels of trust and isolation. Extensions can declare their trust level and permissions in their manifest, and Superset will load them in the appropriate sandbox:
- **Core (Tier 1)**: Trusted extensions run in the main context with full access
- **Iframe (Tier 2)**: Semi-trusted extensions run in browser-sandboxed iframes
- **WASM (Tier 3)**: Untrusted logic runs in WebAssembly sandboxes
For detailed information about the sandbox system, see [Extension Sandboxing](./sandbox).
## Trust Model
Administrators are responsible for evaluating and verifying the security of any extensions they choose to install. Superset's sandbox system provides defense-in-depth:
1. **Core extensions** require explicit trust configuration and optionally signature verification
2. **Iframe-sandboxed extensions** are isolated by the browser's same-origin policy
3. **WASM-sandboxed extensions** have no access to browser APIs
A directory of community extensions is available in the [Community Extensions](./registry) page. Note that these extensions are not vetted by the Apache Superset project—administrators must evaluate each extension before installation.
## Extension Signing
Extensions can be cryptographically signed to verify their authenticity and integrity. This is required for extensions that need `core` trust level in production environments with signature verification enabled.
- **Developers**: See [Extension Signing](./signing) to learn how to sign your extensions
- **Administrators**: See [Administrator Configuration](./admin-configuration) to configure trusted signers
## Administrator Configuration
Superset provides extensive configuration options for controlling extension trust levels, signature verification, and security policies. Key settings include:
- **Trusted extensions list**: Extensions allowed to run as `core`
- **Signature verification**: Require valid signatures for core trust
- **Default trust level**: Sandbox level for unlisted extensions
For complete configuration details, see [Administrator Configuration](./admin-configuration).
## Security Reporting
We plan to introduce an optional sandboxed execution model for extensions in the future (as part of an additional SIP). Until then, administrators should exercise caution and follow best practices when selecting and deploying third-party extensions. A directory of community extensions is available in the [Community Extensions](./registry) page. Note that these extensions are not vetted by the Apache Superset project—administrators must evaluate each extension before installation.
**Any performance or security vulnerabilities introduced by external extensions should be reported directly to the extension author, not as Superset vulnerabilities.**

View File

@@ -1,236 +0,0 @@
---
title: Extension Signing
sidebar_position: 11
---
<!--
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.
-->
# Extension Signing
Signing your extension allows administrators to verify its authenticity and integrity. Signed extensions can run as `core` trust level in production environments where signature verification is required.
## Why Sign Extensions?
- **Trust**: Administrators can verify your extension comes from a known source
- **Integrity**: Ensures the extension hasn't been modified since you signed it
- **Core Access**: Required for extensions needing `core` trust level in secured deployments
- **Distribution**: Makes your extension suitable for enterprise environments
## Generating Signing Keys
Generate a new Ed25519 keypair for signing your extensions:
```bash
superset-extensions generate-keys --output my-signing-key.pem
```
This creates two files:
| File | Purpose | Share? |
|------|---------|--------|
| `my-signing-key.pem` | Private key for signing | **Never share!** |
| `my-signing-key.pub` | Public key for verification | Share with administrators |
**Output example:**
```
✅ Private key: my-signing-key.pem
✅ Public key: my-signing-key.pub
Fingerprint: MCowBQYDK2Vw...
⚠️ Keep the private key secure! Only share the public key with administrators.
Usage:
Sign an extension: superset-extensions bundle --sign my-signing-key.pem
Share with admins: my-signing-key.pub
```
## Signing an Extension
### During Bundle
The easiest way to sign is during the bundle step:
```bash
superset-extensions bundle --sign my-signing-key.pem
```
This builds, signs the manifest, and creates the `.supx` bundle in one command.
**Output:**
```
✅ Full build completed in dist/
✅ Manifest signed
✅ Bundle created (signed): my-extension-1.0.0.supx
```
### Signing Existing Manifest
To sign an already-built manifest:
```bash
superset-extensions sign --key my-signing-key.pem --manifest dist/manifest.json
```
This creates `dist/manifest.sig` containing the signature.
## Bundle Structure
A signed extension bundle contains:
```
my-extension-1.0.0.supx
├── manifest.json # Extension manifest
├── manifest.sig # Ed25519 signature (base64-encoded)
├── frontend/dist/ # Frontend assets
└── backend/src/ # Backend code (if applicable)
```
The signature file (`manifest.sig`) contains a base64-encoded Ed25519 signature of the manifest content.
## Distributing Your Public Key
Share your public key (`.pub` file) with administrators who want to trust your extensions:
1. **Direct sharing**: Send the `.pub` file via secure channels
2. **Documentation**: Include in your extension's README
3. **Website**: Host on your organization's website with HTTPS
Administrators will add your public key to their `EXTENSIONS_TRUST_CONFIG.trusted_signers` configuration.
### Key Fingerprint
The fingerprint helps administrators verify they have the correct key. Include it in your documentation:
```
Public Key Fingerprint: MCowBQYDK2Vw...
```
Administrators should verify this fingerprint matches when adding your key.
## Security Best Practices
### Protect Your Private Key
- **Never commit** private keys to version control
- **Use secure storage** like hardware security modules (HSM) for production keys
- **Limit access** to the private key to authorized personnel only
- **Back up securely** in case of key loss
### Key Rotation
Consider rotating keys periodically:
1. Generate a new keypair
2. Notify administrators of the new public key
3. Sign new releases with the new key
4. Keep the old key available for verifying existing releases
### Multiple Keys
For organizations, consider separate keys for:
- Development/testing releases
- Production releases
- Different product teams
## Requesting Core Trust
If your extension needs `core` trust level:
1. **Sign your extension** using the process above
2. **Document your public key** with fingerprint
3. **Explain why core is needed** in your extension documentation
4. **Provide your public key** to administrators
Administrators will then:
1. Add your public key to `trusted_signers`
2. Enable `require_core_signatures: True`
3. Your signed extension can now run as `core`
## Verification Process
When Superset loads your extension:
1. Reads `manifest.json` and `manifest.sig` from the bundle
2. Checks if the extension requests `core` trust level
3. If `require_core_signatures` is enabled, verifies the signature
4. Checks the signature against all keys in `trusted_signers`
5. If verification passes, grants the requested trust level
6. If verification fails, downgrades to `default_trust_level`
## Troubleshooting
### "Signature verification failed"
- Ensure you're using the matching private key for the public key given to admins
- Verify the manifest wasn't modified after signing
- Check that the `.sig` file was included in the bundle
### "Private key must be Ed25519"
- The signing system only supports Ed25519 keys
- Generate a new key using `superset-extensions generate-keys`
### Administrator Reports Invalid Signature
- Verify the public key file wasn't corrupted during transfer
- Confirm the fingerprint matches between your key and theirs
- Re-sign the extension and redistribute
## Technical Details
### Signature Algorithm
Extensions use **Ed25519** signatures:
- Fast signature generation and verification
- Small signature size (64 bytes)
- Strong security guarantees
- Deterministic signatures (same input always produces same output)
### Signature Format
The `manifest.sig` file contains:
```
<base64-encoded Ed25519 signature>
```
The signature is computed over the raw bytes of `manifest.json`.
### Key Format
Keys are stored in PEM format:
**Private key:**
```
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEI...
-----END PRIVATE KEY-----
```
**Public key:**
```
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA...
-----END PUBLIC KEY-----
```

View File

@@ -26,6 +26,9 @@ module.exports = {
collapsed: true,
items: [
'contributing/overview',
'guidelines/design-guidelines',
'guidelines/frontend-style-guidelines',
'guidelines/backend-style-guidelines',
],
},
{
@@ -49,17 +52,7 @@ module.exports = {
'extensions/development',
'extensions/deployment',
'extensions/mcp',
{
type: 'category',
label: 'Security',
collapsed: true,
items: [
'extensions/security',
'extensions/sandbox',
'extensions/signing',
'extensions/admin-configuration',
],
},
'extensions/security',
'extensions/registry',
],
},
@@ -71,5 +64,20 @@ module.exports = {
'testing/overview',
],
},
{
type: 'category',
label: 'UI Components',
collapsed: true,
link: {
type: 'doc',
id: 'components/index',
},
items: [
{
type: 'autogenerated',
dirName: 'components',
},
],
},
],
};

View File

@@ -0,0 +1,114 @@
---
title: Storybook
sidebar_position: 5
---
<!--
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.
-->
# Storybook
Superset uses [Storybook](https://storybook.js.org/) for developing and testing UI components in isolation. Storybook provides a sandbox to build components independently, outside of the main application.
## Public Storybook
A public Storybook with components from the `master` branch is available at:
**[apache-superset.github.io/superset-ui](https://apache-superset.github.io/superset-ui/?path=/story/*)**
## Running Locally
### Main Superset Storybook
To run the main Superset Storybook locally:
```bash
cd superset-frontend
# Start Storybook (opens at http://localhost:6006)
npm run storybook
# Build static Storybook
npm run build-storybook
```
### @superset-ui Package Storybook
The `@superset-ui` packages have a separate Storybook for component library development:
```bash
cd superset-frontend
# Install dependencies and bootstrap packages
npm ci && npm run bootstrap
# Start the @superset-ui Storybook (opens at http://localhost:9001)
cd packages/superset-ui-demo
npm run storybook
```
## Adding Stories
### To an Existing Package
If stories already exist for the package, extend the `examples` array in the package's story file:
```
storybook/stories/<package>/index.js
```
### To a New Package
1. Add package dependencies:
```bash
npm install <package>
```
2. Create a story folder matching the package name:
```bash
mkdir storybook/stories/superset-ui-<package>/
```
3. Create an `index.js` file with the story configuration:
```javascript
export default {
examples: [
{
storyPath: '@superset-ui/package',
storyName: 'My Story',
renderStory: () => <MyComponent />,
},
],
};
```
Use the `|` separator for nested stories:
```javascript
storyPath: '@superset-ui/package|Category|Subcategory'
```
## Best Practices
- **Isolate components**: Stories should render components in isolation, without application context
- **Show variations**: Create stories for different states, sizes, and configurations
- **Document props**: Use Storybook's controls to expose configurable props
- **Test edge cases**: Include stories for loading states, error states, and empty states

View File

@@ -1,40 +1,590 @@
---
title: API
title: API Reference
hide_title: true
sidebar_position: 10
---
import SwaggerUI from 'swagger-ui-react';
import openapi from '/resources/openapi.json';
import 'swagger-ui-react/swagger-ui.css';
import { Alert } from 'antd';
## API
## REST API Reference
Superset's public **REST API** follows the
[OpenAPI specification](https://swagger.io/specification/), and is
documented here. The docs below are generated using
[Swagger React UI](https://www.npmjs.com/package/swagger-ui-react).
:::resources
- [Blog: The Superset REST API](https://preset.io/blog/2020-10-01-superset-api/)
- [Blog: Accessing APIs with Superset](https://preset.io/blog/accessing-apis-with-superset/)
:::
Superset exposes a comprehensive **REST API** that follows the [OpenAPI specification](https://swagger.io/specification/).
You can use this API to programmatically interact with Superset for automation, integrations, and custom applications.
<Alert
type="info"
message={
<div>
<strong>NOTE! </strong>
You can find an interactive version of this documentation on your local Superset
instance at <strong>/swagger/v1</strong> (unless disabled)
</div>
showIcon
message="Code Samples & Schema Documentation"
description={
<span>
Each endpoint includes ready-to-use code samples in <strong>cURL</strong>, <strong>Python</strong>, and <strong>JavaScript</strong>.
The sidebar includes <strong>Schema definitions</strong> for detailed data model documentation.
</span>
}
style={{ marginBottom: '24px' }}
/>
<br />
<br />
<hr />
<div className="swagger-container">
<SwaggerUI spec={openapi} />
</div>
---
### Authentication
Most API endpoints require authentication via JWT tokens.
#### Quick Start
```bash
# 1. Get a JWT token
curl -X POST http://localhost:8088/api/v1/security/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "admin", "provider": "db"}'
# 2. Use the access_token from the response
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
http://localhost:8088/api/v1/dashboard/
```
#### Security Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get the CSRF token](./api/get-the-csrf-token) | `/api/v1/security/csrf_token/` |
| `POST` | [Get a guest token](./api/get-a-guest-token) | `/api/v1/security/guest_token/` |
| `POST` | [Create security login](./api/create-security-login) | `/api/v1/security/login` |
| `POST` | [Create security refresh](./api/create-security-refresh) | `/api/v1/security/refresh` |
---
### API Endpoints
#### Core Resources
<details>
<summary><strong>Dashboards</strong> (26 endpoints) — Create, read, update, and delete dashboards.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete dashboards](./api/bulk-delete-dashboards) | `/api/v1/dashboard/` |
| `GET` | [Get a list of dashboards](./api/get-a-list-of-dashboards) | `/api/v1/dashboard/` |
| `POST` | [Create a new dashboard](./api/create-a-new-dashboard) | `/api/v1/dashboard/` |
| `GET` | [Get metadata information about this API resource (dashboard--info)](./api/get-metadata-information-about-this-api-resource-dashboard-info) | `/api/v1/dashboard/_info` |
| `GET` | [Get a dashboard detail information](./api/get-a-dashboard-detail-information) | `/api/v1/dashboard/{id_or_slug}` |
| `GET` | [Get a dashboard's chart definitions.](./api/get-a-dashboards-chart-definitions) | `/api/v1/dashboard/{id_or_slug}/charts` |
| `POST` | [Create a copy of an existing dashboard](./api/create-a-copy-of-an-existing-dashboard) | `/api/v1/dashboard/{id_or_slug}/copy/` |
| `GET` | [Get dashboard's datasets](./api/get-dashboards-datasets) | `/api/v1/dashboard/{id_or_slug}/datasets` |
| `DELETE` | [Delete a dashboard's embedded configuration](./api/delete-a-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `GET` | [Get the dashboard's embedded configuration](./api/get-the-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `POST` | [Set a dashboard's embedded configuration](./api/set-a-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `PUT` | [Update dashboard by id_or_slug embedded](./api/update-dashboard-by-id-or-slug-embedded) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `GET` | [Get dashboard's tabs](./api/get-dashboards-tabs) | `/api/v1/dashboard/{id_or_slug}/tabs` |
| `DELETE` | [Delete a dashboard](./api/delete-a-dashboard) | `/api/v1/dashboard/{pk}` |
| `PUT` | [Update a dashboard](./api/update-a-dashboard) | `/api/v1/dashboard/{pk}` |
| `POST` | [Compute and cache a screenshot (dashboard-pk-cache-dashboard-screenshot)](./api/compute-and-cache-a-screenshot-dashboard-pk-cache-dashboard-screenshot) | `/api/v1/dashboard/{pk}/cache_dashboard_screenshot/` |
| `PUT` | [Update colors configuration for a dashboard.](./api/update-colors-configuration-for-a-dashboard) | `/api/v1/dashboard/{pk}/colors` |
| `DELETE` | [Remove the dashboard from the user favorite list](./api/remove-the-dashboard-from-the-user-favorite-list) | `/api/v1/dashboard/{pk}/favorites/` |
| `POST` | [Mark the dashboard as favorite for the current user](./api/mark-the-dashboard-as-favorite-for-the-current-user) | `/api/v1/dashboard/{pk}/favorites/` |
| `PUT` | [Update native filters configuration for a dashboard.](./api/update-native-filters-configuration-for-a-dashboard) | `/api/v1/dashboard/{pk}/filters` |
| `GET` | [Get a computed screenshot from cache (dashboard-pk-screenshot-digest)](./api/get-a-computed-screenshot-from-cache-dashboard-pk-screenshot-digest) | `/api/v1/dashboard/{pk}/screenshot/{digest}/` |
| `GET` | [Get dashboard's thumbnail](./api/get-dashboards-thumbnail) | `/api/v1/dashboard/{pk}/thumbnail/{digest}/` |
| `GET` | [Download multiple dashboards as YAML files](./api/download-multiple-dashboards-as-yaml-files) | `/api/v1/dashboard/export/` |
| `GET` | [Check favorited dashboards for current user](./api/check-favorited-dashboards-for-current-user) | `/api/v1/dashboard/favorite_status/` |
| `POST` | [Import dashboard(s) with associated charts/datasets/databases](./api/import-dashboard-s-with-associated-charts-datasets-databases) | `/api/v1/dashboard/import/` |
| `GET` | [Get related fields data (dashboard-related-column-name)](./api/get-related-fields-data-dashboard-related-column-name) | `/api/v1/dashboard/related/{column_name}` |
</details>
<details>
<summary><strong>Charts</strong> (20 endpoints) — Create, read, update, and delete charts (slices).</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete charts](./api/bulk-delete-charts) | `/api/v1/chart/` |
| `GET` | [Get a list of charts](./api/get-a-list-of-charts) | `/api/v1/chart/` |
| `POST` | [Create a new chart](./api/create-a-new-chart) | `/api/v1/chart/` |
| `GET` | [Get metadata information about this API resource (chart--info)](./api/get-metadata-information-about-this-api-resource-chart-info) | `/api/v1/chart/_info` |
| `DELETE` | [Delete a chart](./api/delete-a-chart) | `/api/v1/chart/{pk}` |
| `GET` | [Get a chart detail information](./api/get-a-chart-detail-information) | `/api/v1/chart/{pk}` |
| `PUT` | [Update a chart](./api/update-a-chart) | `/api/v1/chart/{pk}` |
| `GET` | [Compute and cache a screenshot (chart-pk-cache-screenshot)](./api/compute-and-cache-a-screenshot-chart-pk-cache-screenshot) | `/api/v1/chart/{pk}/cache_screenshot/` |
| `GET` | [Return payload data response for a chart](./api/return-payload-data-response-for-a-chart) | `/api/v1/chart/{pk}/data/` |
| `DELETE` | [Remove the chart from the user favorite list](./api/remove-the-chart-from-the-user-favorite-list) | `/api/v1/chart/{pk}/favorites/` |
| `POST` | [Mark the chart as favorite for the current user](./api/mark-the-chart-as-favorite-for-the-current-user) | `/api/v1/chart/{pk}/favorites/` |
| `GET` | [Get a computed screenshot from cache (chart-pk-screenshot-digest)](./api/get-a-computed-screenshot-from-cache-chart-pk-screenshot-digest) | `/api/v1/chart/{pk}/screenshot/{digest}/` |
| `GET` | [Get chart thumbnail](./api/get-chart-thumbnail) | `/api/v1/chart/{pk}/thumbnail/{digest}/` |
| `POST` | [Return payload data response for the given query (chart-data)](./api/return-payload-data-response-for-the-given-query-chart-data) | `/api/v1/chart/data` |
| `GET` | [Return payload data response for the given query (chart-data-cache-key)](./api/return-payload-data-response-for-the-given-query-chart-data-cache-key) | `/api/v1/chart/data/{cache_key}` |
| `GET` | [Download multiple charts as YAML files](./api/download-multiple-charts-as-yaml-files) | `/api/v1/chart/export/` |
| `GET` | [Check favorited charts for current user](./api/check-favorited-charts-for-current-user) | `/api/v1/chart/favorite_status/` |
| `POST` | [Import chart(s) with associated datasets and databases](./api/import-chart-s-with-associated-datasets-and-databases) | `/api/v1/chart/import/` |
| `GET` | [Get related fields data (chart-related-column-name)](./api/get-related-fields-data-chart-related-column-name) | `/api/v1/chart/related/{column_name}` |
| `PUT` | [Warm up the cache for the chart](./api/warm-up-the-cache-for-the-chart) | `/api/v1/chart/warm_up_cache` |
</details>
<details>
<summary><strong>Datasets</strong> (18 endpoints) — Manage datasets (tables) used for building charts.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete datasets](./api/bulk-delete-datasets) | `/api/v1/dataset/` |
| `GET` | [Get a list of datasets](./api/get-a-list-of-datasets) | `/api/v1/dataset/` |
| `POST` | [Create a new dataset](./api/create-a-new-dataset) | `/api/v1/dataset/` |
| `GET` | [Get metadata information about this API resource (dataset--info)](./api/get-metadata-information-about-this-api-resource-dataset-info) | `/api/v1/dataset/_info` |
| `DELETE` | [Delete a dataset](./api/delete-a-dataset) | `/api/v1/dataset/{pk}` |
| `GET` | [Get a dataset](./api/get-a-dataset) | `/api/v1/dataset/{pk}` |
| `PUT` | [Update a dataset](./api/update-a-dataset) | `/api/v1/dataset/{pk}` |
| `DELETE` | [Delete a dataset column](./api/delete-a-dataset-column) | `/api/v1/dataset/{pk}/column/{column_id}` |
| `DELETE` | [Delete a dataset metric](./api/delete-a-dataset-metric) | `/api/v1/dataset/{pk}/metric/{metric_id}` |
| `PUT` | [Refresh and update columns of a dataset](./api/refresh-and-update-columns-of-a-dataset) | `/api/v1/dataset/{pk}/refresh` |
| `GET` | [Get charts and dashboards count associated to a dataset](./api/get-charts-and-dashboards-count-associated-to-a-dataset) | `/api/v1/dataset/{pk}/related_objects` |
| `GET` | [Get distinct values from field data (dataset-distinct-column-name)](./api/get-distinct-values-from-field-data-dataset-distinct-column-name) | `/api/v1/dataset/distinct/{column_name}` |
| `POST` | [Duplicate a dataset](./api/duplicate-a-dataset) | `/api/v1/dataset/duplicate` |
| `GET` | [Download multiple datasets as YAML files](./api/download-multiple-datasets-as-yaml-files) | `/api/v1/dataset/export/` |
| `POST` | [Retrieve a table by name, or create it if it does not exist](./api/retrieve-a-table-by-name-or-create-it-if-it-does-not-exist) | `/api/v1/dataset/get_or_create/` |
| `POST` | [Import dataset(s) with associated databases](./api/import-dataset-s-with-associated-databases) | `/api/v1/dataset/import/` |
| `GET` | [Get related fields data (dataset-related-column-name)](./api/get-related-fields-data-dataset-related-column-name) | `/api/v1/dataset/related/{column_name}` |
| `PUT` | [Warm up the cache for each chart powered by the given table](./api/warm-up-the-cache-for-each-chart-powered-by-the-given-table) | `/api/v1/dataset/warm_up_cache` |
</details>
<details>
<summary><strong>Database</strong> (31 endpoints) — Manage database connections and metadata.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get a list of databases](./api/get-a-list-of-databases) | `/api/v1/database/` |
| `POST` | [Create a new database](./api/create-a-new-database) | `/api/v1/database/` |
| `GET` | [Get metadata information about this API resource (database--info)](./api/get-metadata-information-about-this-api-resource-database-info) | `/api/v1/database/_info` |
| `DELETE` | [Delete a database](./api/delete-a-database) | `/api/v1/database/{pk}` |
| `GET` | [Get a database](./api/get-a-database) | `/api/v1/database/{pk}` |
| `PUT` | [Change a database](./api/change-a-database) | `/api/v1/database/{pk}` |
| `GET` | [Get all catalogs from a database](./api/get-all-catalogs-from-a-database) | `/api/v1/database/{pk}/catalogs/` |
| `GET` | [Get a database connection info](./api/get-a-database-connection-info) | `/api/v1/database/{pk}/connection` |
| `GET` | [Get function names supported by a database](./api/get-function-names-supported-by-a-database) | `/api/v1/database/{pk}/function_names/` |
| `GET` | [Get charts and dashboards count associated to a database](./api/get-charts-and-dashboards-count-associated-to-a-database) | `/api/v1/database/{pk}/related_objects/` |
| `GET` | [The list of the database schemas where to upload information](./api/the-list-of-the-database-schemas-where-to-upload-information) | `/api/v1/database/{pk}/schemas_access_for_file_upload/` |
| `GET` | [Get all schemas from a database](./api/get-all-schemas-from-a-database) | `/api/v1/database/{pk}/schemas/` |
| `GET` | [Get database select star for table (database-pk-select-star-table-name)](./api/get-database-select-star-for-table-database-pk-select-star-table-name) | `/api/v1/database/{pk}/select_star/{table_name}/` |
| `GET` | [Get database select star for table (database-pk-select-star-table-name-schema-name)](./api/get-database-select-star-for-table-database-pk-select-star-table-name-schema-name) | `/api/v1/database/{pk}/select_star/{table_name}/{schema_name}/` |
| `DELETE` | [Delete a SSH tunnel](./api/delete-a-ssh-tunnel) | `/api/v1/database/{pk}/ssh_tunnel/` |
| `POST` | [Re-sync all permissions for a database connection](./api/re-sync-all-permissions-for-a-database-connection) | `/api/v1/database/{pk}/sync_permissions/` |
| `GET` | [Get table extra metadata (database-pk-table-extra-table-name-schema-name)](./api/get-table-extra-metadata-database-pk-table-extra-table-name-schema-name) | `/api/v1/database/{pk}/table_extra/{table_name}/{schema_name}/` |
| `GET` | [Get table metadata](./api/get-table-metadata) | `/api/v1/database/{pk}/table_metadata/` |
| `GET` | [Get table extra metadata (database-pk-table-metadata-extra)](./api/get-table-extra-metadata-database-pk-table-metadata-extra) | `/api/v1/database/{pk}/table_metadata/extra/` |
| `GET` | [Get database table metadata](./api/get-database-table-metadata) | `/api/v1/database/{pk}/table/{table_name}/{schema_name}/` |
| `GET` | [Get a list of tables for given database](./api/get-a-list-of-tables-for-given-database) | `/api/v1/database/{pk}/tables/` |
| `POST` | [Upload a file to a database table](./api/upload-a-file-to-a-database-table) | `/api/v1/database/{pk}/upload/` |
| `POST` | [Validate arbitrary SQL](./api/validate-arbitrary-sql) | `/api/v1/database/{pk}/validate_sql/` |
| `GET` | [Get names of databases currently available](./api/get-names-of-databases-currently-available) | `/api/v1/database/available/` |
| `GET` | [Download database(s) and associated dataset(s) as a zip file](./api/download-database-s-and-associated-dataset-s-as-a-zip-file) | `/api/v1/database/export/` |
| `POST` | [Import database(s) with associated datasets](./api/import-database-s-with-associated-datasets) | `/api/v1/database/import/` |
| `GET` | [Receive personal access tokens from OAuth2](./api/receive-personal-access-tokens-from-o-auth-2) | `/api/v1/database/oauth2/` |
| `GET` | [Get related fields data (database-related-column-name)](./api/get-related-fields-data-database-related-column-name) | `/api/v1/database/related/{column_name}` |
| `POST` | [Test a database connection](./api/test-a-database-connection) | `/api/v1/database/test_connection/` |
| `POST` | [Upload a file and returns file metadata](./api/upload-a-file-and-returns-file-metadata) | `/api/v1/database/upload_metadata/` |
| `POST` | [Validate database connection parameters](./api/validate-database-connection-parameters) | `/api/v1/database/validate_parameters/` |
</details>
#### Data Exploration
<details>
<summary><strong>Explore</strong> (1 endpoints) — Chart exploration and data querying endpoints.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Assemble Explore related information in a single endpoint](./api/assemble-explore-related-information-in-a-single-endpoint) | `/api/v1/explore/` |
</details>
<details>
<summary><strong>SQL Lab</strong> (6 endpoints) — Execute SQL queries and manage SQL Lab sessions.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get the bootstrap data for SqlLab page](./api/get-the-bootstrap-data-for-sql-lab-page) | `/api/v1/sqllab/` |
| `POST` | [Estimate the SQL query execution cost](./api/estimate-the-sql-query-execution-cost) | `/api/v1/sqllab/estimate/` |
| `POST` | [Execute a SQL query](./api/execute-a-sql-query) | `/api/v1/sqllab/execute/` |
| `GET` | [Export the SQL query results to a CSV](./api/export-the-sql-query-results-to-a-csv) | `/api/v1/sqllab/export/{client_id}/` |
| `POST` | [Format SQL code](./api/format-sql-code) | `/api/v1/sqllab/format_sql/` |
| `GET` | [Get the result of a SQL query execution](./api/get-the-result-of-a-sql-query-execution) | `/api/v1/sqllab/results/` |
</details>
<details>
<summary><strong>Queries</strong> (17 endpoints) — View and manage SQL Lab query history.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get a list of queries](./api/get-a-list-of-queries) | `/api/v1/query/` |
| `GET` | [Get query detail information](./api/get-query-detail-information) | `/api/v1/query/{pk}` |
| `GET` | [Get distinct values from field data (query-distinct-column-name)](./api/get-distinct-values-from-field-data-query-distinct-column-name) | `/api/v1/query/distinct/{column_name}` |
| `GET` | [Get related fields data (query-related-column-name)](./api/get-related-fields-data-query-related-column-name) | `/api/v1/query/related/{column_name}` |
| `POST` | [Manually stop a query with client_id](./api/manually-stop-a-query-with-client-id) | `/api/v1/query/stop` |
| `GET` | [Get a list of queries that changed after last_updated_ms](./api/get-a-list-of-queries-that-changed-after-last-updated-ms) | `/api/v1/query/updated_since` |
| `DELETE` | [Bulk delete saved queries](./api/bulk-delete-saved-queries) | `/api/v1/saved_query/` |
| `GET` | [Get a list of saved queries](./api/get-a-list-of-saved-queries) | `/api/v1/saved_query/` |
| `POST` | [Create a saved query](./api/create-a-saved-query) | `/api/v1/saved_query/` |
| `GET` | [Get metadata information about this API resource (saved-query--info)](./api/get-metadata-information-about-this-api-resource-saved-query-info) | `/api/v1/saved_query/_info` |
| `DELETE` | [Delete a saved query](./api/delete-a-saved-query) | `/api/v1/saved_query/{pk}` |
| `GET` | [Get a saved query](./api/get-a-saved-query) | `/api/v1/saved_query/{pk}` |
| `PUT` | [Update a saved query](./api/update-a-saved-query) | `/api/v1/saved_query/{pk}` |
| `GET` | [Get distinct values from field data (saved-query-distinct-column-name)](./api/get-distinct-values-from-field-data-saved-query-distinct-column-name) | `/api/v1/saved_query/distinct/{column_name}` |
| `GET` | [Download multiple saved queries as YAML files](./api/download-multiple-saved-queries-as-yaml-files) | `/api/v1/saved_query/export/` |
| `POST` | [Import saved queries with associated databases](./api/import-saved-queries-with-associated-databases) | `/api/v1/saved_query/import/` |
| `GET` | [Get related fields data (saved-query-related-column-name)](./api/get-related-fields-data-saved-query-related-column-name) | `/api/v1/saved_query/related/{column_name}` |
</details>
<details>
<summary><strong>Datasources</strong> (1 endpoints) — Query datasource metadata and column values.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get possible values for a datasource column](./api/get-possible-values-for-a-datasource-column) | `/api/v1/datasource/{datasource_type}/{datasource_id}/column/{column_name}/values/` |
</details>
<details>
<summary><strong>Advanced Data Type</strong> (2 endpoints) — Endpoints for advanced data type operations and conversions.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Return an AdvancedDataTypeResponse](./api/return-an-advanced-data-type-response) | `/api/v1/advanced_data_type/convert` |
| `GET` | [Return a list of available advanced data types](./api/return-a-list-of-available-advanced-data-types) | `/api/v1/advanced_data_type/types` |
</details>
#### Organization & Customization
<details>
<summary><strong>Tags</strong> (15 endpoints) — Organize assets with tags.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete tags](./api/bulk-delete-tags) | `/api/v1/tag/` |
| `GET` | [Get a list of tags](./api/get-a-list-of-tags) | `/api/v1/tag/` |
| `POST` | [Create a tag](./api/create-a-tag) | `/api/v1/tag/` |
| `GET` | [Get metadata information about tag API endpoints](./api/get-metadata-information-about-tag-api-endpoints) | `/api/v1/tag/_info` |
| `POST` | [Add tags to an object](./api/add-tags-to-an-object) | `/api/v1/tag/{object_type}/{object_id}/` |
| `DELETE` | [Delete a tagged object](./api/delete-a-tagged-object) | `/api/v1/tag/{object_type}/{object_id}/{tag}/` |
| `DELETE` | [Delete a tag](./api/delete-a-tag) | `/api/v1/tag/{pk}` |
| `GET` | [Get a tag detail information](./api/get-a-tag-detail-information) | `/api/v1/tag/{pk}` |
| `PUT` | [Update a tag](./api/update-a-tag) | `/api/v1/tag/{pk}` |
| `DELETE` | [Delete tag by pk favorites](./api/delete-tag-by-pk-favorites) | `/api/v1/tag/{pk}/favorites/` |
| `POST` | [Create tag by pk favorites](./api/create-tag-by-pk-favorites) | `/api/v1/tag/{pk}/favorites/` |
| `POST` | [Bulk create tags and tagged objects](./api/bulk-create-tags-and-tagged-objects) | `/api/v1/tag/bulk_create` |
| `GET` | [Get tag favorite status](./api/get-tag-favorite-status) | `/api/v1/tag/favorite_status/` |
| `GET` | [Get all objects associated with a tag](./api/get-all-objects-associated-with-a-tag) | `/api/v1/tag/get_objects/` |
| `GET` | [Get related fields data (tag-related-column-name)](./api/get-related-fields-data-tag-related-column-name) | `/api/v1/tag/related/{column_name}` |
</details>
<details>
<summary><strong>Annotation Layers</strong> (14 endpoints) — Manage annotation layers and annotations for charts.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Delete multiple annotation layers in a bulk operation](./api/delete-multiple-annotation-layers-in-a-bulk-operation) | `/api/v1/annotation_layer/` |
| `GET` | [Get a list of annotation layers (annotation-layer)](./api/get-a-list-of-annotation-layers-annotation-layer) | `/api/v1/annotation_layer/` |
| `POST` | [Create an annotation layer (annotation-layer)](./api/create-an-annotation-layer-annotation-layer) | `/api/v1/annotation_layer/` |
| `GET` | [Get metadata information about this API resource (annotation-layer--info)](./api/get-metadata-information-about-this-api-resource-annotation-layer-info) | `/api/v1/annotation_layer/_info` |
| `DELETE` | [Delete annotation layer (annotation-layer-pk)](./api/delete-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
| `GET` | [Get an annotation layer (annotation-layer-pk)](./api/get-an-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
| `PUT` | [Update an annotation layer (annotation-layer-pk)](./api/update-an-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
| `DELETE` | [Bulk delete annotation layers](./api/bulk-delete-annotation-layers) | `/api/v1/annotation_layer/{pk}/annotation/` |
| `GET` | [Get a list of annotation layers (annotation-layer-pk-annotation)](./api/get-a-list-of-annotation-layers-annotation-layer-pk-annotation) | `/api/v1/annotation_layer/{pk}/annotation/` |
| `POST` | [Create an annotation layer (annotation-layer-pk-annotation)](./api/create-an-annotation-layer-annotation-layer-pk-annotation) | `/api/v1/annotation_layer/{pk}/annotation/` |
| `DELETE` | [Delete annotation layer (annotation-layer-pk-annotation-annotation-id)](./api/delete-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
| `GET` | [Get an annotation layer (annotation-layer-pk-annotation-annotation-id)](./api/get-an-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
| `PUT` | [Update an annotation layer (annotation-layer-pk-annotation-annotation-id)](./api/update-an-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
| `GET` | [Get related fields data (annotation-layer-related-column-name)](./api/get-related-fields-data-annotation-layer-related-column-name) | `/api/v1/annotation_layer/related/{column_name}` |
</details>
<details>
<summary><strong>CSS Templates</strong> (8 endpoints) — Manage CSS templates for custom dashboard styling.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete CSS templates](./api/bulk-delete-css-templates) | `/api/v1/css_template/` |
| `GET` | [Get a list of CSS templates](./api/get-a-list-of-css-templates) | `/api/v1/css_template/` |
| `POST` | [Create a CSS template](./api/create-a-css-template) | `/api/v1/css_template/` |
| `GET` | [Get metadata information about this API resource (css-template--info)](./api/get-metadata-information-about-this-api-resource-css-template-info) | `/api/v1/css_template/_info` |
| `DELETE` | [Delete a CSS template](./api/delete-a-css-template) | `/api/v1/css_template/{pk}` |
| `GET` | [Get a CSS template](./api/get-a-css-template) | `/api/v1/css_template/{pk}` |
| `PUT` | [Update a CSS template](./api/update-a-css-template) | `/api/v1/css_template/{pk}` |
| `GET` | [Get related fields data (css-template-related-column-name)](./api/get-related-fields-data-css-template-related-column-name) | `/api/v1/css_template/related/{column_name}` |
</details>
#### Sharing & Embedding
<details>
<summary><strong>Dashboard Permanent Link</strong> (2 endpoints) — Create and retrieve permanent links to dashboard states.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a new dashboard's permanent link](./api/create-a-new-dashboards-permanent-link) | `/api/v1/dashboard/{pk}/permalink` |
| `GET` | [Get dashboard's permanent link state](./api/get-dashboards-permanent-link-state) | `/api/v1/dashboard/permalink/{key}` |
</details>
<details>
<summary><strong>Explore Permanent Link</strong> (2 endpoints) — Create and retrieve permanent links to chart explore states.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a new permanent link (explore-permalink)](./api/create-a-new-permanent-link-explore-permalink) | `/api/v1/explore/permalink` |
| `GET` | [Get chart's permanent link state](./api/get-charts-permanent-link-state) | `/api/v1/explore/permalink/{key}` |
</details>
<details>
<summary><strong>SQL Lab Permanent Link</strong> (2 endpoints) — Create and retrieve permanent links to SQL Lab states.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a new permanent link (sqllab-permalink)](./api/create-a-new-permanent-link-sqllab-permalink) | `/api/v1/sqllab/permalink` |
| `GET` | [Get permanent link state for SQLLab editor.](./api/get-permanent-link-state-for-sql-lab-editor) | `/api/v1/sqllab/permalink/{key}` |
</details>
<details>
<summary><strong>Embedded Dashboard</strong> (1 endpoints) — Configure embedded dashboard settings.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get a report schedule log (embedded-dashboard-uuid)](./api/get-a-report-schedule-log-embedded-dashboard-uuid) | `/api/v1/embedded_dashboard/{uuid}` |
</details>
<details>
<summary><strong>Dashboard Filter State</strong> (4 endpoints) — Manage temporary filter state for dashboards.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a dashboard's filter state](./api/create-a-dashboards-filter-state) | `/api/v1/dashboard/{pk}/filter_state` |
| `DELETE` | [Delete a dashboard's filter state value](./api/delete-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
| `GET` | [Get a dashboard's filter state value](./api/get-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
| `PUT` | [Update a dashboard's filter state value](./api/update-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
</details>
<details>
<summary><strong>Explore Form Data</strong> (4 endpoints) — Manage temporary form data for chart exploration.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a new form_data](./api/create-a-new-form-data) | `/api/v1/explore/form_data` |
| `DELETE` | [Delete a form_data](./api/delete-a-form-data) | `/api/v1/explore/form_data/{key}` |
| `GET` | [Get a form_data](./api/get-a-form-data) | `/api/v1/explore/form_data/{key}` |
| `PUT` | [Update an existing form_data](./api/update-an-existing-form-data) | `/api/v1/explore/form_data/{key}` |
</details>
#### Scheduling & Alerts
<details>
<summary><strong>Report Schedules</strong> (11 endpoints) — Configure scheduled reports and alerts.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete report schedules](./api/bulk-delete-report-schedules) | `/api/v1/report/` |
| `GET` | [Get a list of report schedules](./api/get-a-list-of-report-schedules) | `/api/v1/report/` |
| `POST` | [Create a report schedule](./api/create-a-report-schedule) | `/api/v1/report/` |
| `GET` | [Get metadata information about this API resource (report--info)](./api/get-metadata-information-about-this-api-resource-report-info) | `/api/v1/report/_info` |
| `DELETE` | [Delete a report schedule](./api/delete-a-report-schedule) | `/api/v1/report/{pk}` |
| `GET` | [Get a report schedule](./api/get-a-report-schedule) | `/api/v1/report/{pk}` |
| `PUT` | [Update a report schedule](./api/update-a-report-schedule) | `/api/v1/report/{pk}` |
| `GET` | [Get a list of report schedule logs](./api/get-a-list-of-report-schedule-logs) | `/api/v1/report/{pk}/log/` |
| `GET` | [Get a report schedule log (report-pk-log-log-id)](./api/get-a-report-schedule-log-report-pk-log-log-id) | `/api/v1/report/{pk}/log/{log_id}` |
| `GET` | [Get related fields data (report-related-column-name)](./api/get-related-fields-data-report-related-column-name) | `/api/v1/report/related/{column_name}` |
| `GET` | [Get slack channels](./api/get-slack-channels) | `/api/v1/report/slack_channels/` |
</details>
#### Security & Access Control
<details>
<summary><strong>Security Roles</strong> (10 endpoints) — Manage security roles and their permissions.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security roles](./api/get-security-roles) | `/api/v1/security/roles/` |
| `POST` | [Create security roles](./api/create-security-roles) | `/api/v1/security/roles/` |
| `GET` | [Get security roles info](./api/get-security-roles-info) | `/api/v1/security/roles/_info` |
| `DELETE` | [Delete security roles by pk](./api/delete-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
| `GET` | [Get security roles by pk](./api/get-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
| `PUT` | [Update security roles by pk](./api/update-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
| `POST` | [Create security roles by role_id permissions](./api/create-security-roles-by-role-id-permissions) | `/api/v1/security/roles/{role_id}/permissions` |
| `GET` | [Get security roles by role_id permissions](./api/get-security-roles-by-role-id-permissions) | `/api/v1/security/roles/{role_id}/permissions/` |
| `PUT` | [Update security roles by role_id users](./api/update-security-roles-by-role-id-users) | `/api/v1/security/roles/{role_id}/users` |
| `GET` | [List roles](./api/list-roles) | `/api/v1/security/roles/search/` |
</details>
<details>
<summary><strong>Security Users</strong> (6 endpoints) — Manage user accounts.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security users](./api/get-security-users) | `/api/v1/security/users/` |
| `POST` | [Create security users](./api/create-security-users) | `/api/v1/security/users/` |
| `GET` | [Get security users info](./api/get-security-users-info) | `/api/v1/security/users/_info` |
| `DELETE` | [Delete security users by pk](./api/delete-security-users-by-pk) | `/api/v1/security/users/{pk}` |
| `GET` | [Get security users by pk](./api/get-security-users-by-pk) | `/api/v1/security/users/{pk}` |
| `PUT` | [Update security users by pk](./api/update-security-users-by-pk) | `/api/v1/security/users/{pk}` |
</details>
<details>
<summary><strong>Security Permissions</strong> (3 endpoints) — View available permissions.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security permissions](./api/get-security-permissions) | `/api/v1/security/permissions/` |
| `GET` | [Get security permissions info](./api/get-security-permissions-info) | `/api/v1/security/permissions/_info` |
| `GET` | [Get security permissions by pk](./api/get-security-permissions-by-pk) | `/api/v1/security/permissions/{pk}` |
</details>
<details>
<summary><strong>Security Resources (View Menus)</strong> (6 endpoints) — Manage security resources (view menus).</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security resources](./api/get-security-resources) | `/api/v1/security/resources/` |
| `POST` | [Create security resources](./api/create-security-resources) | `/api/v1/security/resources/` |
| `GET` | [Get security resources info](./api/get-security-resources-info) | `/api/v1/security/resources/_info` |
| `DELETE` | [Delete security resources by pk](./api/delete-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
| `GET` | [Get security resources by pk](./api/get-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
| `PUT` | [Update security resources by pk](./api/update-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
</details>
<details>
<summary><strong>Security Permissions on Resources (View Menus)</strong> (6 endpoints) — Manage permission-resource mappings.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security permissions resources](./api/get-security-permissions-resources) | `/api/v1/security/permissions-resources/` |
| `POST` | [Create security permissions resources](./api/create-security-permissions-resources) | `/api/v1/security/permissions-resources/` |
| `GET` | [Get security permissions resources info](./api/get-security-permissions-resources-info) | `/api/v1/security/permissions-resources/_info` |
| `DELETE` | [Delete security permissions resources by pk](./api/delete-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
| `GET` | [Get security permissions resources by pk](./api/get-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
| `PUT` | [Update security permissions resources by pk](./api/update-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
</details>
<details>
<summary><strong>Row Level Security</strong> (8 endpoints) — Manage row-level security rules for data access control.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete RLS rules](./api/bulk-delete-rls-rules) | `/api/v1/rowlevelsecurity/` |
| `GET` | [Get a list of RLS](./api/get-a-list-of-rls) | `/api/v1/rowlevelsecurity/` |
| `POST` | [Create a new RLS rule](./api/create-a-new-rls-rule) | `/api/v1/rowlevelsecurity/` |
| `GET` | [Get metadata information about this API resource (rowlevelsecurity--info)](./api/get-metadata-information-about-this-api-resource-rowlevelsecurity-info) | `/api/v1/rowlevelsecurity/_info` |
| `DELETE` | [Delete an RLS](./api/delete-an-rls) | `/api/v1/rowlevelsecurity/{pk}` |
| `GET` | [Get an RLS](./api/get-an-rls) | `/api/v1/rowlevelsecurity/{pk}` |
| `PUT` | [Update an RLS rule](./api/update-an-rls-rule) | `/api/v1/rowlevelsecurity/{pk}` |
| `GET` | [Get related fields data (rowlevelsecurity-related-column-name)](./api/get-related-fields-data-rowlevelsecurity-related-column-name) | `/api/v1/rowlevelsecurity/related/{column_name}` |
</details>
#### Import/Export & Administration
<details>
<summary><strong>Import/export</strong> (2 endpoints) — Import and export Superset assets (dashboards, charts, databases).</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Export all assets](./api/export-all-assets) | `/api/v1/assets/export/` |
| `POST` | [Import multiple assets](./api/import-multiple-assets) | `/api/v1/assets/import/` |
</details>
<details>
<summary><strong>CacheRestApi</strong> (1 endpoints) — Cache management and invalidation operations.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Invalidate cache records and remove the database records](./api/invalidate-cache-records-and-remove-the-database-records) | `/api/v1/cachekey/invalidate` |
</details>
<details>
<summary><strong>LogRestApi</strong> (4 endpoints) — Access audit logs and activity history.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get a list of logs](./api/get-a-list-of-logs) | `/api/v1/log/` |
| `POST` | [Create log](./api/create-log) | `/api/v1/log/` |
| `GET` | [Get a log detail information](./api/get-a-log-detail-information) | `/api/v1/log/{pk}` |
| `GET` | [Get recent activity data for a user](./api/get-recent-activity-data-for-a-user) | `/api/v1/log/recent_activity/` |
</details>
#### User & System
<details>
<summary><strong>Current User</strong> (2 endpoints) — Get information about the currently authenticated user.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get the user object](./api/get-the-user-object) | `/api/v1/me/` |
| `GET` | [Get the user roles](./api/get-the-user-roles) | `/api/v1/me/roles/` |
</details>
<details>
<summary><strong>User</strong> (1 endpoints) — User profile and preferences.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get the user avatar](./api/get-the-user-avatar) | `/api/v1/user/{user_id}/avatar.png` |
</details>
<details>
<summary><strong>Menu</strong> (1 endpoints) — Get the Superset menu structure.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get menu](./api/get-menu) | `/api/v1/menu/` |
</details>
<details>
<summary><strong>Available Domains</strong> (1 endpoints) — Get available domains for the Superset instance.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get all available domains](./api/get-all-available-domains) | `/api/v1/available_domains/` |
</details>
<details>
<summary><strong>AsyncEventsRestApi</strong> (1 endpoints) — Real-time event streaming via Server-Sent Events (SSE).</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Read off of the Redis events stream](./api/read-off-of-the-redis-events-stream) | `/api/v1/async_event/` |
</details>
<details>
<summary><strong>OpenApi</strong> (1 endpoints) — Access the OpenAPI specification.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get api by version openapi](./api/get-api-by-version-openapi) | `/api/{version}/_openapi` |
</details>
---
### Additional Resources
- [Superset REST API Blog Post](https://preset.io/blog/2020-10-01-superset-api/)
- [Accessing APIs with Superset](https://preset.io/blog/accessing-apis-with-superset/)

View File

@@ -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?

View File

@@ -32,7 +32,7 @@ git clone https://github.com/apache/superset
$ cd superset
# Set the repo to the state associated with the latest official version
$ git checkout tags/5.0.0
$ git checkout tags/6.0.0
# Fire up Superset using Docker Compose
$ docker compose -f docker-compose-image-tag.yml up

View File

@@ -19,9 +19,11 @@
import type { Config } from '@docusaurus/types';
import type { Options, ThemeConfig } from '@docusaurus/preset-classic';
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';
@@ -45,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,
@@ -73,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,
@@ -131,6 +133,12 @@ if (!versionsConfig.developer_portal.disabled && !versionsConfig.developer_porta
docId: 'index',
label: 'Overview',
},
{
type: 'doc',
docsPluginId: 'developer_portal',
docId: 'contributing/overview',
label: 'Contributing',
},
{
type: 'doc',
docsPluginId: 'developer_portal',
@@ -146,14 +154,12 @@ if (!versionsConfig.developer_portal.disabled && !versionsConfig.developer_porta
{
type: 'doc',
docsPluginId: 'developer_portal',
docId: 'guidelines/design-guidelines',
label: 'Guidelines',
docId: 'components/index',
label: 'UI Components',
},
{
type: 'doc',
docsPluginId: 'developer_portal',
docId: 'contributing/overview',
label: 'Contributing',
label: 'API Reference',
href: '/docs/api',
},
],
});
@@ -166,29 +172,137 @@ 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',
'@docusaurus/theme-live-codeblock',
'docusaurus-theme-openapi-docs',
],
plugins: [
require.resolve('./src/webpack.extend.ts'),
...dynamicPlugins,
[
'docusaurus-plugin-less',
'docusaurus-plugin-openapi-docs',
{
lessOptions: {
javascriptEnabled: true,
id: 'api',
docsPluginId: 'classic',
config: {
superset: {
specPath: 'static/resources/openapi.json',
outputDir: 'docs/api',
sidebarOptions: {
groupPathsBy: 'tag',
categoryLinkSource: 'tag',
sidebarCollapsible: true,
sidebarCollapsed: true,
},
showSchemas: true,
hideSendButton: true,
showInfoPage: false,
showExtensions: true,
} satisfies OpenApiPlugin.Options,
},
},
],
...dynamicPlugins,
// 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',
{
@@ -350,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,
@@ -362,6 +476,7 @@ const config: Config = {
disableVersioning: false,
showLastUpdateAuthor: true,
showLastUpdateTime: true,
docItemComponent: '@theme/ApiItem', // Required for OpenAPI docs
},
blog: {
showReadingTime: true,
@@ -372,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,
@@ -473,8 +634,10 @@ const config: Config = {
footer: {
links: [],
copyright: `
<div class="footer__applitools">
We use &nbsp;<a href="https://applitools.com/" target="_blank" rel="nofollow"><img src="/img/applitools.png" title="Applitools" /></a>
<div class="footer__ci-services">
<span>CI powered by</span>
<a href="https://applitools.com/" target="_blank" rel="nofollow noopener noreferrer"><img src="/img/applitools.png" alt="Applitools" title="Applitools - Visual Testing" /></a>
<a href="https://www.netlify.com/" target="_blank" rel="nofollow noopener noreferrer"><img src="/img/netlify.png" alt="Netlify" title="Netlify - Deploy Previews" /></a>
</div>
<p>Copyright © ${new Date().getFullYear()},
The <a href="https://www.apache.org/" target="_blank" rel="noreferrer">Apache Software Foundation</a>,

View File

@@ -1,6 +1,6 @@
{
"copyright": {
"message": "\n <div class=\"footer__applitools\">\n We use &nbsp;<a href=\"https://applitools.com/\" target=\"_blank\" rel=\"nofollow\"><img src=\"/img/applitools.png\" title=\"Applitools\" /></a>\n </div>\n <p>Copyright © 2024,\n The <a href=\"https://www.apache.org/\" target=\"_blank\" rel=\"noreferrer\">Apache Software Foundation</a>,\n Licensed under the Apache <a href=\"https://apache.org/licenses/LICENSE-2.0\" target=\"_blank\" rel=\"noreferrer\">License</a>.</p>\n <p><small>Apache Superset, Apache, Superset, the Superset logo, and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation. All other products or name brands are trademarks of their respective holders, including The Apache Software Foundation.\n <a href=\"https://www.apache.org/\" target=\"_blank\">Apache Software Foundation</a> resources</small></p>\n <img class=\"footer__divider\" src=\"/img/community/line.png\" alt=\"Divider\" />\n <p>\n <small>\n <a href=\"/docs/security/\" target=\"_blank\" rel=\"noreferrer\">Security</a>&nbsp;|&nbsp;\n <a href=\"https://www.apache.org/foundation/sponsorship.html\" target=\"_blank\" rel=\"noreferrer\">Donate</a>&nbsp;|&nbsp;\n <a href=\"https://www.apache.org/foundation/thanks.html\" target=\"_blank\" rel=\"noreferrer\">Thanks</a>&nbsp;|&nbsp;\n <a href=\"https://apache.org/events/current-event\" target=\"_blank\" rel=\"noreferrer\">Events</a>&nbsp;|&nbsp;\n <a href=\"https://apache.org/licenses/\" target=\"_blank\" rel=\"noreferrer\">License</a>&nbsp;|&nbsp;\n <a href=\"https://privacy.apache.org/policies/privacy-policy-public.html\" target=\"_blank\" rel=\"noreferrer\">Privacy</a>\n </small>\n </p>\n <!-- telemetry/analytics pixel: -->\n <img referrerPolicy=\"no-referrer-when-downgrade\" src=\"https://static.scarf.sh/a.png?x-pxid=39ae6855-95fc-4566-86e5-360d542b0a68\" />\n ",
"message": "\n <div class=\"footer__ci-services\">\n <span>CI powered by</span>\n <a href=\"https://applitools.com/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><img src=\"/img/applitools.png\" alt=\"Applitools\" title=\"Applitools - Visual Testing\" /></a>\n <a href=\"https://www.netlify.com/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><img src=\"/img/netlify.png\" alt=\"Netlify\" title=\"Netlify - Deploy Previews\" /></a>\n </div>\n <p>Copyright © 2026,\n The <a href=\"https://www.apache.org/\" target=\"_blank\" rel=\"noreferrer\">Apache Software Foundation</a>,\n Licensed under the Apache <a href=\"https://apache.org/licenses/LICENSE-2.0\" target=\"_blank\" rel=\"noreferrer\">License</a>.</p>\n <p><small>Apache Superset, Apache, Superset, the Superset logo, and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation. All other products or name brands are trademarks of their respective holders, including The Apache Software Foundation.\n <a href=\"https://www.apache.org/\" target=\"_blank\">Apache Software Foundation</a> resources</small></p>\n <img class=\"footer__divider\" src=\"/img/community/line.png\" alt=\"Divider\" />\n <p>\n <small>\n <a href=\"/docs/security/\" target=\"_blank\" rel=\"noreferrer\">Security</a>&nbsp;|&nbsp;\n <a href=\"https://www.apache.org/foundation/sponsorship.html\" target=\"_blank\" rel=\"noreferrer\">Donate</a>&nbsp;|&nbsp;\n <a href=\"https://www.apache.org/foundation/thanks.html\" target=\"_blank\" rel=\"noreferrer\">Thanks</a>&nbsp;|&nbsp;\n <a href=\"https://apache.org/events/current-event\" target=\"_blank\" rel=\"noreferrer\">Events</a>&nbsp;|&nbsp;\n <a href=\"https://apache.org/licenses/\" target=\"_blank\" rel=\"noreferrer\">License</a>&nbsp;|&nbsp;\n <a href=\"https://privacy.apache.org/policies/privacy-policy-public.html\" target=\"_blank\" rel=\"noreferrer\">Privacy</a>\n </small>\n </p>\n <!-- telemetry/analytics pixel: -->\n <img referrerPolicy=\"no-referrer-when-downgrade\" src=\"https://static.scarf.sh/a.png?x-pxid=39ae6855-95fc-4566-86e5-360d542b0a68\" />\n ",
"description": "The footer copyright"
}
}

View File

@@ -34,6 +34,8 @@
NODE_VERSION = "20"
# Yarn version
YARN_VERSION = "1.22.22"
# Increase heap size for webpack bundling of Superset UI components
NODE_OPTIONS = "--max-old-space-size=8192"
# Deploy preview settings
[context.deploy-preview]

View File

@@ -6,19 +6,24 @@
"scripts": {
"docusaurus": "docusaurus",
"_init": "cat src/intro_header.txt ../README.md > docs/intro.md",
"start": "yarn run _init && yarn run generate:extension-components && yarn run generate:database-docs && 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:extension-components && yarn run generate:database-docs && 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",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "yarn run _init && docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "yarn run generate:extension-components && yarn run generate:database-docs && tsc",
"typecheck": "yarn run generate:all && tsc",
"generate:extension-components": "node scripts/generate-extension-components.mjs",
"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 & 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",
@@ -42,40 +47,45 @@
"@emotion/core": "^11.0.0",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.14.1",
"@fontsource/fira-code": "^5.2.7",
"@fontsource/inter": "^5.2.8",
"@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.1",
"caniuse-lite": "^1.0.30001765",
"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": "^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.0.0"
"unist-util-visit": "^5.1.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.9.1",
@@ -89,11 +99,11 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-react": "^7.37.5",
"globals": "^17.0.0",
"globals": "^17.3.0",
"prettier": "^3.8.1",
"typescript": "~5.9.3",
"typescript-eslint": "^8.53.1",
"webpack": "^5.104.1"
"typescript-eslint": "^8.54.0",
"webpack": "^5.105.0"
},
"browserslist": {
"production": [
@@ -107,5 +117,10 @@
"last 1 safari version"
]
},
"resolutions": {
"react-redux": "^9.2.0",
"@reduxjs/toolkit": "^2.5.0",
"baseline-browser-mapping": "^2.9.19"
},
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
}

View 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);
};
}

View 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');
},
};
};

View File

@@ -0,0 +1,123 @@
/**
* 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.
*/
/**
* Convert the generated TypeScript API sidebar to CommonJS format.
* This allows the sidebar to be imported by sidebars.js.
* Also adds unique keys to duplicate labels to avoid translation conflicts.
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const sidebarTsPath = path.join(__dirname, '..', 'docs', 'api', 'sidebar.ts');
const sidebarJsPath = path.join(__dirname, '..', 'docs', 'api', 'sidebar.js');
if (!fs.existsSync(sidebarTsPath)) {
console.log('No sidebar.ts found, skipping conversion');
process.exit(0);
}
let content = fs.readFileSync(sidebarTsPath, 'utf8');
// Remove TypeScript import
content = content.replace(/import type.*\n/g, '');
// Remove type annotation
content = content.replace(/: SidebarsConfig/g, '');
// Change export default to module.exports
content = content.replace(
/export default sidebar\.apisidebar;/,
'module.exports = sidebar.apisidebar;'
);
// Parse the sidebar to add unique keys for duplicate labels
// This avoids translation key conflicts when the same label appears multiple times
try {
// Extract the sidebar object
const sidebarMatch = content.match(/const sidebar = (\{[\s\S]*\});/);
if (sidebarMatch) {
// Use Function constructor instead of eval for safer evaluation
const sidebarObj = new Function(`return ${sidebarMatch[1]}`)();
// First pass: count labels
const countLabels = (items) => {
const counts = {};
const count = (item) => {
if (item.type === 'doc' && item.label) {
counts[item.label] = (counts[item.label] || 0) + 1;
}
if (item.items) {
item.items.forEach(count);
}
};
items.forEach(count);
return counts;
};
const counts = countLabels(sidebarObj.apisidebar);
// Second pass: add keys to items with duplicate labels
const addKeys = (items, prefix = 'api') => {
for (const item of items) {
if (item.type === 'doc' && item.label && counts[item.label] > 1) {
item.key = item.id;
}
// Also add keys to categories to avoid conflicts with main sidebar categories
if (item.type === 'category' && item.label) {
item.key = `${prefix}-category-${item.label.toLowerCase().replace(/\s+/g, '-')}`;
}
if (item.items) {
addKeys(item.items, prefix);
}
}
};
addKeys(sidebarObj.apisidebar);
// Regenerate the content with the updated sidebar
content = `const sidebar = ${JSON.stringify(sidebarObj, null, 2)};
module.exports = sidebar.apisidebar;
`;
}
} catch (e) {
console.warn('Could not add unique keys to sidebar:', e.message);
// Fall back to simple conversion
content = content.replace(
/export default sidebar\.apisidebar;/,
'module.exports = sidebar.apisidebar;'
);
}
// Add header with eslint-disable to allow @ts-nocheck
const header = `/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck
/**
* Auto-generated CommonJS sidebar from sidebar.ts
* Do not edit directly - run 'yarn generate:api-docs' to regenerate
*/
`;
fs.writeFileSync(sidebarJsPath, header + content);
console.log('Converted sidebar.ts to sidebar.js');

View File

@@ -0,0 +1,296 @@
# 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.
"""
Extract custom_errors from database engine specs for documentation.
This script parses engine spec files to extract error handling information
that can be displayed on database documentation pages.
Usage: python scripts/extract_custom_errors.py
Output: JSON mapping of engine spec module names to their custom errors
"""
import ast
import json # noqa: TID251 - standalone docs script, not part of superset
import sys
from pathlib import Path
from typing import Any
# Map SupersetErrorType values to human-readable categories and issue codes
ERROR_TYPE_INFO = {
"CONNECTION_INVALID_USERNAME_ERROR": {
"category": "Authentication",
"description": "Invalid username",
"issue_codes": [1012],
},
"CONNECTION_INVALID_PASSWORD_ERROR": {
"category": "Authentication",
"description": "Invalid password",
"issue_codes": [1013],
},
"CONNECTION_ACCESS_DENIED_ERROR": {
"category": "Authentication",
"description": "Access denied",
"issue_codes": [1014, 1015],
},
"CONNECTION_INVALID_HOSTNAME_ERROR": {
"category": "Connection",
"description": "Invalid hostname",
"issue_codes": [1007],
},
"CONNECTION_PORT_CLOSED_ERROR": {
"category": "Connection",
"description": "Port closed or refused",
"issue_codes": [1008],
},
"CONNECTION_HOST_DOWN_ERROR": {
"category": "Connection",
"description": "Host unreachable",
"issue_codes": [1009],
},
"CONNECTION_UNKNOWN_DATABASE_ERROR": {
"category": "Connection",
"description": "Unknown database",
"issue_codes": [1015],
},
"CONNECTION_DATABASE_PERMISSIONS_ERROR": {
"category": "Permissions",
"description": "Insufficient permissions",
"issue_codes": [1017],
},
"CONNECTION_MISSING_PARAMETERS_ERROR": {
"category": "Configuration",
"description": "Missing parameters",
"issue_codes": [1018],
},
"CONNECTION_DATABASE_TIMEOUT": {
"category": "Connection",
"description": "Connection timeout",
"issue_codes": [1001, 1009],
},
"COLUMN_DOES_NOT_EXIST_ERROR": {
"category": "Query",
"description": "Column not found",
"issue_codes": [1003, 1004],
},
"TABLE_DOES_NOT_EXIST_ERROR": {
"category": "Query",
"description": "Table not found",
"issue_codes": [1003, 1005],
},
"SCHEMA_DOES_NOT_EXIST_ERROR": {
"category": "Query",
"description": "Schema not found",
"issue_codes": [1003, 1016],
},
"SYNTAX_ERROR": {
"category": "Query",
"description": "SQL syntax error",
"issue_codes": [1030],
},
"OBJECT_DOES_NOT_EXIST_ERROR": {
"category": "Query",
"description": "Object not found",
"issue_codes": [1029],
},
"GENERIC_DB_ENGINE_ERROR": {
"category": "General",
"description": "Database engine error",
"issue_codes": [1002],
},
}
def extract_string_from_call(node: ast.Call) -> str | None:
"""Extract string from __() or _() translation calls."""
if not node.args:
return None
arg = node.args[0]
if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
return arg.value
elif isinstance(arg, ast.JoinedStr):
# f-string - try to reconstruct
parts = []
for value in arg.values:
if isinstance(value, ast.Constant):
parts.append(str(value.value))
elif isinstance(value, ast.FormattedValue):
# Just use a placeholder
parts.append("{...}")
return "".join(parts)
return None
def extract_custom_errors_from_file(filepath: Path) -> dict[str, list[dict[str, Any]]]:
"""
Extract custom_errors definitions from a Python engine spec file.
Returns a dict mapping class names to their custom errors list.
"""
results = {}
try:
with open(filepath, "r", encoding="utf-8") as f:
source = f.read()
tree = ast.parse(source)
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
class_name = node.name
for item in node.body:
# Look for custom_errors = { ... }
if (
isinstance(item, ast.AnnAssign)
and isinstance(item.target, ast.Name)
and item.target.id == "custom_errors"
and isinstance(item.value, ast.Dict)
):
errors = extract_errors_from_dict(item.value, source)
if errors:
results[class_name] = errors
# Also handle simple assignment: custom_errors = { ... }
elif (
isinstance(item, ast.Assign)
and len(item.targets) == 1
and isinstance(item.targets[0], ast.Name)
and item.targets[0].id == "custom_errors"
and isinstance(item.value, ast.Dict)
):
errors = extract_errors_from_dict(item.value, source)
if errors:
results[class_name] = errors
except (OSError, SyntaxError, ValueError) as e:
print(f"Error parsing {filepath}: {e}", file=sys.stderr)
return results
def extract_regex_info(key: ast.expr) -> dict[str, Any]:
"""Extract regex pattern info from the dict key."""
if isinstance(key, ast.Name):
return {"regex_name": key.id}
if isinstance(key, ast.Call):
if (
isinstance(key.func, ast.Attribute)
and key.func.attr == "compile"
and key.args
and isinstance(key.args[0], ast.Constant)
):
return {"regex_pattern": key.args[0].value}
return {}
def extract_invalid_fields(extra_node: ast.Dict) -> list[str]:
"""Extract invalid fields from the extra dict."""
for k, v in zip(extra_node.keys, extra_node.values, strict=False):
if (
isinstance(k, ast.Constant)
and k.value == "invalid"
and isinstance(v, ast.List)
):
return [elem.value for elem in v.elts if isinstance(elem, ast.Constant)]
return []
def extract_error_tuple_info(value: ast.Tuple) -> dict[str, Any]:
"""Extract error info from the (message, error_type, extra) tuple."""
result: dict[str, Any] = {}
# First element: message template
msg_node = value.elts[0]
if isinstance(msg_node, ast.Call):
message = extract_string_from_call(msg_node)
if message:
result["message_template"] = message
elif isinstance(msg_node, ast.Constant):
result["message_template"] = msg_node.value
# Second element: SupersetErrorType.SOMETHING
type_node = value.elts[1]
if isinstance(type_node, ast.Attribute):
error_type = type_node.attr
result["error_type"] = error_type
if error_type in ERROR_TYPE_INFO:
type_info = ERROR_TYPE_INFO[error_type]
result["category"] = type_info["category"]
result["description"] = type_info["description"]
result["issue_codes"] = type_info["issue_codes"]
# Third element: extra dict with invalid fields
if len(value.elts) >= 3 and isinstance(value.elts[2], ast.Dict):
invalid_fields = extract_invalid_fields(value.elts[2])
if invalid_fields:
result["invalid_fields"] = invalid_fields
return result
def extract_errors_from_dict(dict_node: ast.Dict, source: str) -> list[dict[str, Any]]:
"""Extract error information from a custom_errors dict AST node."""
errors = []
for key, value in zip(dict_node.keys, dict_node.values, strict=False):
if key is None or value is None:
continue
error_info = extract_regex_info(key)
if isinstance(value, ast.Tuple) and len(value.elts) >= 2:
error_info.update(extract_error_tuple_info(value))
if error_info.get("error_type") and error_info.get("message_template"):
errors.append(error_info)
return errors
def main() -> None:
"""Main function to extract custom_errors from all engine specs."""
# Find the superset root directory
script_dir = Path(__file__).parent
root_dir = script_dir.parent.parent
specs_dir = root_dir / "superset" / "db_engine_specs"
if not specs_dir.exists():
print(f"Error: Engine specs directory not found: {specs_dir}", file=sys.stderr)
sys.exit(1)
all_errors = {}
# Process each Python file in the specs directory
for filepath in sorted(specs_dir.glob("*.py")):
if filepath.name.startswith("_"):
continue
module_name = filepath.stem
class_errors = extract_custom_errors_from_file(filepath)
if class_errors:
# Store errors by module and class
all_errors[module_name] = class_errors
# Output as JSON
print(json.dumps(all_errors, indent=2))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,828 @@
# 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.
"""
Fix missing schema references in the OpenAPI spec.
This script patches the openapi.json file to add any missing schemas
that are referenced but not defined.
"""
import json # noqa: TID251 - standalone docs script
import sys
from pathlib import Path
from typing import Any
def add_missing_schemas(spec: dict[str, Any]) -> tuple[dict[str, Any], list[str]]:
"""Add missing schema definitions to the OpenAPI spec."""
schemas = spec.get("components", {}).get("schemas", {})
fixed = []
# DashboardScreenshotPostSchema - based on superset/dashboards/schemas.py
if "DashboardScreenshotPostSchema" not in schemas:
schemas["DashboardScreenshotPostSchema"] = {
"type": "object",
"properties": {
"dataMask": {
"type": "object",
"description": "An object representing the data mask.",
"additionalProperties": True,
},
"activeTabs": {
"type": "array",
"items": {"type": "string"},
"description": "A list representing active tabs.",
},
"anchor": {
"type": "string",
"description": "A string representing the anchor.",
},
"urlParams": {
"type": "array",
"items": {
"type": "array",
"items": {"type": "string"},
"minItems": 2,
"maxItems": 2,
},
"description": "A list of tuples, each containing two strings.",
},
},
}
fixed.append("DashboardScreenshotPostSchema")
# DashboardNativeFiltersConfigUpdateSchema - based on superset/dashboards/schemas.py
if "DashboardNativeFiltersConfigUpdateSchema" not in schemas:
schemas["DashboardNativeFiltersConfigUpdateSchema"] = {
"type": "object",
"properties": {
"deleted": {
"type": "array",
"items": {"type": "string"},
"description": "List of deleted filter IDs.",
},
"modified": {
"type": "array",
"items": {"type": "object"},
"description": "List of modified filter configurations.",
},
"reordered": {
"type": "array",
"items": {"type": "string"},
"description": "List of filter IDs in new order.",
},
},
}
fixed.append("DashboardNativeFiltersConfigUpdateSchema")
# DashboardColorsConfigUpdateSchema - based on superset/dashboards/schemas.py
if "DashboardColorsConfigUpdateSchema" not in schemas:
schemas["DashboardColorsConfigUpdateSchema"] = {
"type": "object",
"properties": {
"color_namespace": {
"type": "string",
"nullable": True,
"description": "The color namespace.",
},
"color_scheme": {
"type": "string",
"nullable": True,
"description": "The color scheme name.",
},
"map_label_colors": {
"type": "object",
"additionalProperties": {"type": "string"},
"description": "Mapping of labels to colors.",
},
"shared_label_colors": {
"type": "object",
"additionalProperties": {"type": "string"},
"description": "Shared label colors across charts.",
},
"label_colors": {
"type": "object",
"additionalProperties": {"type": "string"},
"description": "Label to color mapping.",
},
"color_scheme_domain": {
"type": "array",
"items": {"type": "string"},
"description": "Color scheme domain values.",
},
},
}
fixed.append("DashboardColorsConfigUpdateSchema")
# FormatQueryPayloadSchema - based on superset/sqllab/schemas.py
if "FormatQueryPayloadSchema" not in schemas:
schemas["FormatQueryPayloadSchema"] = {
"type": "object",
"required": ["sql"],
"properties": {
"sql": {
"type": "string",
"description": "The SQL query to format.",
},
"engine": {
"type": "string",
"nullable": True,
"description": "The database engine.",
},
"database_id": {
"type": "integer",
"nullable": True,
"description": "The database id.",
},
"template_params": {
"type": "string",
"nullable": True,
"description": "The SQL query template params as JSON string.",
},
},
}
fixed.append("FormatQueryPayloadSchema")
# get_slack_channels_schema - based on superset/reports/schemas.py
if "get_slack_channels_schema" not in schemas:
schemas["get_slack_channels_schema"] = {
"type": "object",
"properties": {
"search_string": {
"type": "string",
"description": "String to search for in channel names.",
},
"types": {
"type": "array",
"items": {
"type": "string",
"enum": ["public_channel", "private_channel"],
},
"description": "Types of channels to search.",
},
"exact_match": {
"type": "boolean",
"description": "Whether to match channel names exactly.",
},
},
}
fixed.append("get_slack_channels_schema")
if "components" not in spec:
spec["components"] = {}
spec["components"]["schemas"] = schemas
return spec, fixed
def path_to_operation_id(path: str, method: str) -> str:
"""Convert a path and method to an operationId."""
# Remove /api/v1/ prefix
clean_path = path.replace("/api/v1/", "").strip("/")
# Replace path parameters
clean_path = clean_path.replace("{", "by_").replace("}", "")
# Create operation name
method_prefix = {
"get": "get",
"post": "create",
"put": "update",
"delete": "delete",
"patch": "patch",
}.get(method.lower(), method.lower())
return f"{method_prefix}_{clean_path}".replace("/", "_").replace("-", "_")
def path_to_summary(path: str, method: str) -> str:
"""Generate a human-readable summary from path and method."""
# Remove /api/v1/ prefix
clean_path = path.replace("/api/v1/", "").strip("/")
# Handle path parameters
parts = []
for part in clean_path.split("/"):
if part.startswith("{") and part.endswith("}"):
param = part[1:-1]
parts.append(f"by {param}")
else:
parts.append(part.replace("_", " ").replace("-", " "))
resource = " ".join(parts)
method_verb = {
"get": "Get",
"post": "Create",
"put": "Update",
"delete": "Delete",
"patch": "Update",
}.get(method.lower(), method.capitalize())
return f"{method_verb} {resource}"
def add_missing_operation_ids(spec: dict[str, Any]) -> int:
"""Add operationId and summary to operations that are missing them."""
fixed_count = 0
for path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ["get", "post", "put", "delete", "patch"]:
continue
if not isinstance(details, dict):
continue
summary = details.get("summary")
operation_id = details.get("operationId")
if not summary and not operation_id:
details["operationId"] = path_to_operation_id(path, method)
details["summary"] = path_to_summary(path, method)
fixed_count += 1
return fixed_count
TAG_DESCRIPTIONS = {
"Advanced Data Type": "Advanced data type operations and conversions.",
"Annotation Layers": "Manage annotation layers and annotations for charts.",
"AsyncEventsRestApi": "Real-time event streaming via Server-Sent Events (SSE).",
"Available Domains": "Get available domains for the Superset instance.",
"CSS Templates": "Manage CSS templates for custom dashboard styling.",
"CacheRestApi": "Cache management and invalidation operations.",
"Charts": "Create, read, update, and delete charts (slices).",
"Current User": "Get information about the authenticated user.",
"Dashboard Filter State": "Manage temporary filter state for dashboards.",
"Dashboard Permanent Link": "Permanent links to dashboard states.",
"Dashboards": "Create, read, update, and delete dashboards.",
"Database": "Manage database connections and metadata.",
"Datasets": "Manage datasets (tables) used for building charts.",
"Datasources": "Query datasource metadata and column values.",
"Embedded Dashboard": "Configure embedded dashboard settings.",
"Explore": "Chart exploration and data querying endpoints.",
"Explore Form Data": "Manage temporary form data for chart exploration.",
"Explore Permanent Link": "Permanent links to chart explore states.",
"Import/export": "Import and export Superset assets.",
"LogRestApi": "Access audit logs and activity history.",
"Menu": "Get the Superset menu structure.",
"OpenApi": "Access the OpenAPI specification.",
"Queries": "View and manage SQL Lab query history.",
"Report Schedules": "Configure scheduled reports and alerts.",
"Row Level Security": "Manage row-level security rules for data access.",
"SQL Lab": "Execute SQL queries and manage SQL Lab sessions.",
"SQL Lab Permanent Link": "Permanent links to SQL Lab states.",
"Security": "Authentication and token management.",
"Security Permissions": "View available permissions.",
"Security Permissions on Resources (View Menus)": "Permission-resource mappings.",
"Security Resources (View Menus)": "Manage security resources (view menus).",
"Security Roles": "Manage security roles and their permissions.",
"Security Users": "Manage user accounts.",
"Tags": "Organize assets with tags.",
"User": "User profile and preferences.",
}
def generate_code_sample(
method: str, path: str, has_body: bool = False
) -> list[dict[str, str]]:
"""Generate code samples for an endpoint in multiple languages."""
# Clean up path for display
example_path = path.replace("{pk}", "1").replace("{id_or_slug}", "1")
samples = []
# cURL sample
curl_cmd = f'curl -X {method.upper()} "http://localhost:8088{example_path}"'
curl_cmd += ' \\\n -H "Authorization: Bearer $ACCESS_TOKEN"'
if has_body:
curl_cmd += ' \\\n -H "Content-Type: application/json"'
curl_cmd += ' \\\n -d \'{"key": "value"}\''
samples.append(
{
"lang": "cURL",
"label": "cURL",
"source": curl_cmd,
}
)
# Python sample
if method.lower() == "get":
python_code = f"""import requests
response = requests.get(
"http://localhost:8088{example_path}",
headers={{"Authorization": "Bearer " + access_token}}
)
print(response.json())"""
elif method.lower() == "post":
python_code = f"""import requests
response = requests.post(
"http://localhost:8088{example_path}",
headers={{"Authorization": "Bearer " + access_token}},
json={{"key": "value"}}
)
print(response.json())"""
elif method.lower() == "put":
python_code = f"""import requests
response = requests.put(
"http://localhost:8088{example_path}",
headers={{"Authorization": "Bearer " + access_token}},
json={{"key": "value"}}
)
print(response.json())"""
elif method.lower() == "delete":
python_code = f"""import requests
response = requests.delete(
"http://localhost:8088{example_path}",
headers={{"Authorization": "Bearer " + access_token}}
)
print(response.status_code)"""
else:
python_code = f"""import requests
response = requests.{method.lower()}(
"http://localhost:8088{example_path}",
headers={{"Authorization": "Bearer " + access_token}}
)
print(response.json())"""
samples.append(
{
"lang": "Python",
"label": "Python",
"source": python_code,
}
)
# JavaScript sample
if method.lower() == "get":
js_code = f"""const response = await fetch(
"http://localhost:8088{example_path}",
{{
headers: {{
"Authorization": `Bearer ${{accessToken}}`
}}
}}
);
const data = await response.json();
console.log(data);"""
elif method.lower() in ["post", "put", "patch"]:
js_code = f"""const response = await fetch(
"http://localhost:8088{example_path}",
{{
method: "{method.upper()}",
headers: {{
"Authorization": `Bearer ${{accessToken}}`,
"Content-Type": "application/json"
}},
body: JSON.stringify({{ key: "value" }})
}}
);
const data = await response.json();
console.log(data);"""
else:
js_code = f"""const response = await fetch(
"http://localhost:8088{example_path}",
{{
method: "{method.upper()}",
headers: {{
"Authorization": `Bearer ${{accessToken}}`
}}
}}
);
console.log(response.status);"""
samples.append(
{
"lang": "JavaScript",
"label": "JavaScript",
"source": js_code,
}
)
return samples
def add_code_samples(spec: dict[str, Any]) -> int:
"""Add code samples to all endpoints."""
count = 0
for path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ["get", "post", "put", "delete", "patch"]:
continue
if not isinstance(details, dict):
continue
# Skip if already has code samples
if "x-codeSamples" in details:
continue
# Check if endpoint has a request body
has_body = "requestBody" in details
details["x-codeSamples"] = generate_code_sample(method, path, has_body)
count += 1
return count
def configure_servers(spec: dict[str, Any]) -> bool:
"""Configure server URLs with variables for flexible API testing."""
new_servers = [
{
"url": "http://localhost:8088",
"description": "Local development server",
},
{
"url": "{protocol}://{host}:{port}",
"description": "Custom server",
"variables": {
"protocol": {
"default": "http",
"enum": ["http", "https"],
"description": "HTTP protocol",
},
"host": {
"default": "localhost",
"description": "Server hostname or IP",
},
"port": {
"default": "8088",
"description": "Server port",
},
},
},
]
# Check if already configured
existing = spec.get("servers", [])
if len(existing) >= 2 and any("variables" in s for s in existing):
return False
spec["servers"] = new_servers
return True
def add_tag_definitions(spec: dict[str, Any]) -> int:
"""Add tag definitions with descriptions to the OpenAPI spec."""
# Collect all unique tags used in operations
used_tags: set[str] = set()
for _path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ["get", "post", "put", "delete", "patch"]:
continue
if not isinstance(details, dict):
continue
tags = details.get("tags", [])
used_tags.update(tags)
# Create tag definitions
tag_definitions = []
for tag in sorted(used_tags):
tag_def = {"name": tag}
if tag in TAG_DESCRIPTIONS:
tag_def["description"] = TAG_DESCRIPTIONS[tag]
else:
# Generate a generic description
tag_def["description"] = f"Endpoints related to {tag}."
tag_definitions.append(tag_def)
# Only update if we have new tags
existing_tags = {t.get("name") for t in spec.get("tags", [])}
new_tags = [t for t in tag_definitions if t["name"] not in existing_tags]
if new_tags or not spec.get("tags"):
spec["tags"] = tag_definitions
return len(tag_definitions)
return 0
def generate_example_from_schema( # noqa: C901
schema: dict[str, Any],
spec: dict[str, Any],
depth: int = 0,
max_depth: int = 5,
) -> dict[str, Any] | list[Any] | str | int | float | bool | None:
"""Generate an example value from an OpenAPI schema definition."""
if depth > max_depth:
return None
# Handle $ref
if "$ref" in schema:
ref_path = schema["$ref"]
if ref_path.startswith("#/components/schemas/"):
schema_name = ref_path.split("/")[-1]
ref_schema = (
spec.get("components", {}).get("schemas", {}).get(schema_name, {})
)
return generate_example_from_schema(ref_schema, spec, depth + 1, max_depth)
return None
# If schema already has an example, use it
if "example" in schema:
return schema["example"]
schema_type = schema.get("type", "object")
if schema_type == "object":
properties = schema.get("properties", {})
if not properties:
# Check for additionalProperties
if schema.get("additionalProperties"):
return {"key": "value"}
return {}
result = {}
for prop_name, prop_schema in properties.items():
# Limit object depth and skip large nested objects
if depth < max_depth:
example_val = generate_example_from_schema(
prop_schema, spec, depth + 1, max_depth
)
if example_val is not None:
result[prop_name] = example_val
return result
elif schema_type == "array":
items_schema = schema.get("items", {})
if items_schema:
item_example = generate_example_from_schema(
items_schema, spec, depth + 1, max_depth
)
if item_example is not None:
return [item_example]
return []
elif schema_type == "string":
# Check for enum
if "enum" in schema:
return schema["enum"][0]
# Check for format
fmt = schema.get("format", "")
if fmt == "date-time":
return "2024-01-15T10:30:00Z"
elif fmt == "date":
return "2024-01-15"
elif fmt == "email":
return "user@example.com"
elif fmt == "uri" or fmt == "url":
return "https://example.com"
elif fmt == "uuid":
return "550e8400-e29b-41d4-a716-446655440000"
# Use description hints or prop name
return "string"
elif schema_type == "integer":
if "minimum" in schema:
return schema["minimum"]
return 1
elif schema_type == "number":
if "minimum" in schema:
return schema["minimum"]
return 1.0
elif schema_type == "boolean":
return True
elif schema_type == "null":
return None
# Handle oneOf, anyOf
if "oneOf" in schema and schema["oneOf"]:
return generate_example_from_schema(
schema["oneOf"][0], spec, depth + 1, max_depth
)
if "anyOf" in schema and schema["anyOf"]:
return generate_example_from_schema(
schema["anyOf"][0], spec, depth + 1, max_depth
)
return None
def add_response_examples(spec: dict[str, Any]) -> int: # noqa: C901
"""Add example values to API responses for better documentation."""
count = 0
# First, add examples to standard error responses in components
standard_errors = {
"400": {"message": "Bad request: Invalid parameters provided"},
"401": {"message": "Unauthorized: Authentication required"},
"403": {
"message": "Forbidden: You don't have permission to access this resource"
},
"404": {"message": "Not found: The requested resource does not exist"},
"422": {"message": "Unprocessable entity: Validation error"},
"500": {"message": "Internal server error: An unexpected error occurred"},
}
responses = spec.get("components", {}).get("responses", {})
for code, example_value in standard_errors.items():
if code in responses:
response = responses[code]
content = response.get("content", {}).get("application/json", {})
if content and "example" not in content:
content["example"] = example_value
count += 1
# Now add examples to inline response schemas in operations
for _path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ["get", "post", "put", "delete", "patch"]:
continue
if not isinstance(details, dict):
continue
responses_dict = details.get("responses", {})
for _status_code, response in responses_dict.items():
# Skip $ref responses (already handled above)
if "$ref" in response:
continue
content = response.get("content", {}).get("application/json", {})
if not content:
continue
# Skip if already has an example
if "example" in content:
continue
schema = content.get("schema", {})
if schema:
example = generate_example_from_schema(
schema, spec, depth=0, max_depth=3
)
if example is not None and example != {}:
content["example"] = example
count += 1
return count
def add_request_body_examples(spec: dict[str, Any]) -> int:
"""Add example values to API request bodies for better documentation."""
count = 0
for _path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ["post", "put", "patch"]:
continue
if not isinstance(details, dict):
continue
request_body = details.get("requestBody", {})
if not request_body or "$ref" in request_body:
continue
content = request_body.get("content", {}).get("application/json", {})
if not content:
continue
# Skip if already has an example
if "example" in content:
continue
schema = content.get("schema", {})
if schema:
example = generate_example_from_schema(
schema, spec, depth=0, max_depth=4
)
if example is not None and example != {}:
content["example"] = example
count += 1
return count
def make_summaries_unique(spec: dict[str, Any]) -> int: # noqa: C901
"""Make duplicate summaries unique by adding context from the path."""
summary_info: dict[str, list[tuple[str, str]]] = {}
fixed_count = 0
# First pass: collect all summaries and their paths (regardless of method)
for path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ["get", "post", "put", "delete", "patch"]:
continue
if not isinstance(details, dict):
continue
summary = details.get("summary")
if summary:
if summary not in summary_info:
summary_info[summary] = []
summary_info[summary].append((path, method))
# Second pass: make duplicate summaries unique
for path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ["get", "post", "put", "delete", "patch"]:
continue
if not isinstance(details, dict):
continue
summary = details.get("summary")
if summary and len(summary_info.get(summary, [])) > 1:
# Create a unique suffix from the full path
# e.g., /api/v1/chart/{pk}/cache_screenshot/ -> "chart-cache-screenshot"
clean_path = path.replace("/api/v1/", "").strip("/")
# Remove parameter placeholders and convert to slug
clean_path = clean_path.replace("{", "").replace("}", "")
path_slug = clean_path.replace("/", "-").replace("_", "-")
# Check if this suffix is already in the summary
if path_slug not in summary.lower():
new_summary = f"{summary} ({path_slug})"
details["summary"] = new_summary
fixed_count += 1
return fixed_count
def main() -> None: # noqa: C901
"""Main function to fix the OpenAPI spec."""
script_dir = Path(__file__).parent
spec_path = script_dir.parent / "static" / "resources" / "openapi.json"
if not spec_path.exists():
print(f"Error: OpenAPI spec not found at {spec_path}", file=sys.stderr)
sys.exit(1)
print(f"Reading OpenAPI spec from {spec_path}")
with open(spec_path, encoding="utf-8") as f:
spec = json.load(f)
spec, fixed_schemas = add_missing_schemas(spec)
fixed_ops = add_missing_operation_ids(spec)
fixed_tags = add_tag_definitions(spec)
fixed_servers = configure_servers(spec)
changes_made = False
if fixed_servers:
print("Configured server URLs with variables for flexible API testing")
changes_made = True
if fixed_samples := add_code_samples(spec):
print(f"Added code samples to {fixed_samples} endpoints")
changes_made = True
if fixed_examples := add_response_examples(spec):
print(f"Added example JSON responses to {fixed_examples} response schemas")
changes_made = True
if fixed_request_examples := add_request_body_examples(spec):
print(f"Added example JSON to {fixed_request_examples} request bodies")
changes_made = True
if fixed_schemas:
print(f"Added missing schemas: {', '.join(fixed_schemas)}")
changes_made = True
if fixed_ops:
print(f"Added operationId/summary to {fixed_ops} operations")
changes_made = True
if fixed_tags:
print(f"Added {fixed_tags} tag definitions with descriptions")
changes_made = True
if fixed_summaries := make_summaries_unique(spec):
print(f"Made {fixed_summaries} duplicate summaries unique")
changes_made = True
if changes_made:
with open(spec_path, "w", encoding="utf-8") as f:
json.dump(spec, f, indent=2)
f.write("\n") # Ensure trailing newline for pre-commit
print(f"Updated {spec_path}")
else:
print("No fixes needed")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,277 @@
/**
* 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.
*/
/**
* Generates a comprehensive API index MDX file from the OpenAPI spec.
* This creates the api.mdx landing page with all endpoints organized by category.
*
* Uses the generated sidebar to get correct endpoint slugs (the plugin's
* slug algorithm differs from a simple slugify, e.g. handling apostrophes
* and camelCase differently).
*/
import fs from 'fs';
import path from 'path';
import { createRequire } from 'module';
import { fileURLToPath } from 'url';
const require = createRequire(import.meta.url);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SPEC_PATH = path.join(__dirname, '..', 'static', 'resources', 'openapi.json');
const SIDEBAR_PATH = path.join(__dirname, '..', 'docs', 'api', 'sidebar.js');
const OUTPUT_PATH = path.join(__dirname, '..', 'docs', 'api.mdx');
// Category groupings for better organization
const CATEGORY_GROUPS = {
'Authentication': ['Security'],
'Core Resources': ['Dashboards', 'Charts', 'Datasets', 'Database'],
'Data Exploration': ['Explore', 'SQL Lab', 'Queries', 'Datasources', 'Advanced Data Type'],
'Organization & Customization': ['Tags', 'Annotation Layers', 'CSS Templates'],
'Sharing & Embedding': [
'Dashboard Permanent Link', 'Explore Permanent Link', 'SQL Lab Permanent Link',
'Embedded Dashboard', 'Dashboard Filter State', 'Explore Form Data'
],
'Scheduling & Alerts': ['Report Schedules'],
'Security & Access Control': [
'Security Roles', 'Security Users', 'Security Permissions',
'Security Resources (View Menus)', 'Security Permissions on Resources (View Menus)',
'Row Level Security'
],
'Import/Export & Administration': ['Import/export', 'CacheRestApi', 'LogRestApi'],
'User & System': ['Current User', 'User', 'Menu', 'Available Domains', 'AsyncEventsRestApi', 'OpenApi'],
};
/**
* Build a map from sidebar label → doc slug by reading the generated sidebar.
* This ensures we use the exact same slugs that docusaurus-openapi-docs generated.
*/
function buildSlugMap() {
const labelToSlug = {};
try {
const sidebar = require(SIDEBAR_PATH);
const extractDocs = (items) => {
for (const item of items) {
if (item.type === 'doc' && item.label && item.id) {
// id is like "api/create-security-login" → slug "create-security-login"
const slug = item.id.replace(/^api\//, '');
labelToSlug[item.label] = slug;
}
if (item.items) extractDocs(item.items);
}
};
extractDocs(sidebar);
console.log(`Loaded ${Object.keys(labelToSlug).length} slug mappings from sidebar`);
} catch {
console.warn('Could not read sidebar, will use computed slugs');
}
return labelToSlug;
}
function slugify(text) {
return text
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '');
}
function main() {
console.log(`Reading OpenAPI spec from ${SPEC_PATH}`);
const spec = JSON.parse(fs.readFileSync(SPEC_PATH, 'utf-8'));
// Build slug map from the generated sidebar
const labelToSlug = buildSlugMap();
// Build a map of tag -> endpoints
const tagEndpoints = {};
const tagDescriptions = {};
// Get tag descriptions
for (const tag of spec.tags || []) {
tagDescriptions[tag.name] = tag.description || '';
}
// Collect endpoints by tag
for (const [pathUrl, methods] of Object.entries(spec.paths || {})) {
for (const [method, details] of Object.entries(methods)) {
if (!['get', 'post', 'put', 'delete', 'patch'].includes(method)) continue;
const tags = details.tags || ['Untagged'];
const summary = details.summary || `${method.toUpperCase()} ${pathUrl}`;
// Use sidebar slug if available, fall back to computed slug
const slug = labelToSlug[summary] || slugify(summary);
for (const tag of tags) {
if (!tagEndpoints[tag]) {
tagEndpoints[tag] = [];
}
tagEndpoints[tag].push({
method: method.toUpperCase(),
path: pathUrl,
summary,
slug,
});
}
}
}
// Sort endpoints within each tag by path
for (const tag of Object.keys(tagEndpoints)) {
tagEndpoints[tag].sort((a, b) => a.path.localeCompare(b.path));
}
// Generate MDX content
let mdx = `---
title: API Reference
hide_title: true
sidebar_position: 10
---
import { Alert } from 'antd';
## REST API Reference
Superset exposes a comprehensive **REST API** that follows the [OpenAPI specification](https://swagger.io/specification/).
You can use this API to programmatically interact with Superset for automation, integrations, and custom applications.
<Alert
type="info"
showIcon
message="Code Samples & Schema Documentation"
description={
<span>
Each endpoint includes ready-to-use code samples in <strong>cURL</strong>, <strong>Python</strong>, and <strong>JavaScript</strong>.
The sidebar includes <strong>Schema definitions</strong> for detailed data model documentation.
</span>
}
style={{ marginBottom: '24px' }}
/>
---
`;
// Track which tags we've rendered
const renderedTags = new Set();
// Render Authentication first (it's critical for using the API)
mdx += `### Authentication
Most API endpoints require authentication via JWT tokens.
#### Quick Start
\`\`\`bash
# 1. Get a JWT token
curl -X POST http://localhost:8088/api/v1/security/login \\
-H "Content-Type: application/json" \\
-d '{"username": "admin", "password": "admin", "provider": "db"}'
# 2. Use the access_token from the response
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \\
http://localhost:8088/api/v1/dashboard/
\`\`\`
#### Security Endpoints
`;
// Render Security tag endpoints
if (tagEndpoints['Security']) {
mdx += `| Method | Endpoint | Description |\n`;
mdx += `|--------|----------|-------------|\n`;
for (const ep of tagEndpoints['Security']) {
mdx += `| \`${ep.method}\` | [${ep.summary}](./api/${ep.slug}) | \`${ep.path}\` |\n`;
}
mdx += '\n';
renderedTags.add('Security');
}
mdx += `---\n\n### API Endpoints\n\n`;
// Render each category group
for (const [groupName, groupTags] of Object.entries(CATEGORY_GROUPS)) {
if (groupName === 'Authentication') continue; // Already rendered
const tagsInGroup = groupTags.filter(tag => tagEndpoints[tag] && !renderedTags.has(tag));
if (tagsInGroup.length === 0) continue;
mdx += `#### ${groupName}\n\n`;
for (const tag of tagsInGroup) {
const description = tagDescriptions[tag] || '';
const endpoints = tagEndpoints[tag];
mdx += `<details>\n`;
mdx += `<summary><strong>${tag}</strong> (${endpoints.length} endpoints) — ${description}</summary>\n\n`;
mdx += `| Method | Endpoint | Description |\n`;
mdx += `|--------|----------|-------------|\n`;
for (const ep of endpoints) {
mdx += `| \`${ep.method}\` | [${ep.summary}](./api/${ep.slug}) | \`${ep.path}\` |\n`;
}
mdx += `\n</details>\n\n`;
renderedTags.add(tag);
}
}
// Render any remaining tags not in a group
const remainingTags = Object.keys(tagEndpoints).filter(tag => !renderedTags.has(tag));
if (remainingTags.length > 0) {
mdx += `#### Other\n\n`;
for (const tag of remainingTags.sort()) {
const description = tagDescriptions[tag] || '';
const endpoints = tagEndpoints[tag];
mdx += `<details>\n`;
mdx += `<summary><strong>${tag}</strong> (${endpoints.length} endpoints) — ${description}</summary>\n\n`;
mdx += `| Method | Endpoint | Description |\n`;
mdx += `|--------|----------|-------------|\n`;
for (const ep of endpoints) {
mdx += `| \`${ep.method}\` | [${ep.summary}](./api/${ep.slug}) | \`${ep.path}\` |\n`;
}
mdx += `\n</details>\n\n`;
}
}
mdx += `---
### Additional Resources
- [Superset REST API Blog Post](https://preset.io/blog/2020-10-01-superset-api/)
- [Accessing APIs with Superset](https://preset.io/blog/accessing-apis-with-superset/)
`;
// Write output
fs.writeFileSync(OUTPUT_PATH, mdx);
console.log(`Generated API index at ${OUTPUT_PATH}`);
console.log(`Total tags: ${Object.keys(tagEndpoints).length}`);
console.log(`Total endpoints: ${Object.values(tagEndpoints).flat().length}`);
}
main();

View File

@@ -0,0 +1,176 @@
/**
* 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.
*/
/**
* Replaces auto-generated tag pages (DocCardList cards) with endpoint tables
* showing HTTP method, endpoint name, and URI path for each endpoint in the tag.
*
* Runs after `docusaurus gen-api-docs` and `convert-api-sidebar.mjs`.
* Uses the generated sidebar to get correct endpoint slugs.
*/
import fs from 'fs';
import path from 'path';
import { createRequire } from 'module';
import { fileURLToPath } from 'url';
const require = createRequire(import.meta.url);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SPEC_PATH = path.join(__dirname, '..', 'static', 'resources', 'openapi.json');
const API_DOCS_DIR = path.join(__dirname, '..', 'docs', 'api');
const SIDEBAR_PATH = path.join(API_DOCS_DIR, 'sidebar.js');
function slugify(text) {
return text
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '');
}
/**
* Build a map from sidebar label → doc slug by reading the generated sidebar.
*/
function buildSlugMap() {
const labelToSlug = {};
try {
const sidebar = require(SIDEBAR_PATH);
const extractDocs = (items) => {
for (const item of items) {
if (item.type === 'doc' && item.label && item.id) {
const slug = item.id.replace(/^api\//, '');
labelToSlug[item.label] = slug;
}
if (item.items) extractDocs(item.items);
}
};
extractDocs(sidebar);
} catch {
console.warn('Could not read sidebar, will use computed slugs');
}
return labelToSlug;
}
function main() {
console.log('Generating API tag pages with endpoint tables...');
const spec = JSON.parse(fs.readFileSync(SPEC_PATH, 'utf-8'));
const labelToSlug = buildSlugMap();
// Build tag descriptions from the spec
const tagDescriptions = {};
for (const tag of spec.tags || []) {
tagDescriptions[tag.name] = tag.description || '';
}
// Build tag → endpoints map
const tagEndpoints = {};
for (const [pathUrl, methods] of Object.entries(spec.paths || {})) {
for (const [method, details] of Object.entries(methods)) {
if (!['get', 'post', 'put', 'delete', 'patch'].includes(method)) continue;
const tags = details.tags || ['Untagged'];
const summary = details.summary || `${method.toUpperCase()} ${pathUrl}`;
const slug = labelToSlug[summary] || slugify(summary);
for (const tag of tags) {
if (!tagEndpoints[tag]) {
tagEndpoints[tag] = [];
}
tagEndpoints[tag].push({
method: method.toUpperCase(),
path: pathUrl,
summary,
slug,
});
}
}
}
// Sort endpoints within each tag by path then method
for (const tag of Object.keys(tagEndpoints)) {
tagEndpoints[tag].sort((a, b) =>
a.path.localeCompare(b.path) || a.method.localeCompare(b.method)
);
}
// Scan existing .tag.mdx files and match by frontmatter title
const tagFiles = fs.readdirSync(API_DOCS_DIR)
.filter(f => f.endsWith('.tag.mdx'));
let updated = 0;
for (const tagFile of tagFiles) {
const tagFilePath = path.join(API_DOCS_DIR, tagFile);
const existing = fs.readFileSync(tagFilePath, 'utf-8');
// Extract frontmatter
const frontmatterMatch = existing.match(/^---\n([\s\S]*?)\n---/);
if (!frontmatterMatch) {
console.warn(` No frontmatter in ${tagFile}, skipping`);
continue;
}
const frontmatter = frontmatterMatch[1];
// Extract the title from frontmatter (this matches the spec tag name)
const titleMatch = frontmatter.match(/title:\s*"([^"]+)"/);
if (!titleMatch) {
console.warn(` No title in ${tagFile}, skipping`);
continue;
}
const tagName = titleMatch[1];
const endpoints = tagEndpoints[tagName];
if (!endpoints || endpoints.length === 0) {
console.warn(` No endpoints found for tag "${tagName}" (${tagFile})`);
continue;
}
const description = tagDescriptions[tagName] || '';
// Build the endpoint table
let table = '| Method | Endpoint | Path |\n';
table += '|--------|----------|------|\n';
for (const ep of endpoints) {
table += `| \`${ep.method}\` | [${ep.summary}](./${ep.slug}) | \`${ep.path}\` |\n`;
}
// Generate the new MDX content
const mdx = `---
${frontmatter}
---
${description}
${table}
`;
fs.writeFileSync(tagFilePath, mdx);
updated++;
}
console.log(`Updated ${updated} tag pages with endpoint tables`);
}
main();

View File

@@ -30,9 +30,12 @@
import { spawnSync } from 'child_process';
import fs from 'fs';
import { createRequire } from 'module';
import path from 'path';
import { fileURLToPath } from 'url';
const require = createRequire(import.meta.url);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const ROOT_DIR = path.resolve(__dirname, '../..');
@@ -41,6 +44,7 @@ const DATA_OUTPUT_DIR = path.join(DOCS_DIR, 'src/data');
const DATA_OUTPUT_FILE = path.join(DATA_OUTPUT_DIR, 'databases.json');
const MDX_OUTPUT_DIR = path.join(DOCS_DIR, 'docs/databases');
const MDX_SUPPORTED_DIR = path.join(MDX_OUTPUT_DIR, 'supported');
const IMAGES_DIR = path.join(DOCS_DIR, 'static/img/databases');
/**
* Try to run the full lib.py script with Flask context
@@ -607,30 +611,109 @@ const README_PATH = path.join(ROOT_DIR, 'README.md');
const README_START_MARKER = '<!-- SUPPORTED_DATABASES_START -->';
const README_END_MARKER = '<!-- SUPPORTED_DATABASES_END -->';
/**
* Read image dimensions, with fallback SVG viewBox parsing for cases where
* image-size can't handle SVG width/height attributes (e.g., scientific notation).
*/
function getImageDimensions(imgPath) {
const sizeOf = require('image-size');
try {
const dims = sizeOf(imgPath);
// image-size may misparse SVG attributes (e.g. width="1e3" → 1).
// Fall back to viewBox parsing if a dimension looks wrong.
if (dims.type === 'svg' && (dims.width < 2 || dims.height < 2)) {
const content = fs.readFileSync(imgPath, 'utf-8');
const vbMatch = content.match(/viewBox=["']([^"']+)["']/);
if (vbMatch) {
const parts = vbMatch[1].trim().split(/[\s,]+/).map(Number);
if (parts.length >= 4 && parts[2] > 0 && parts[3] > 0) {
return { width: parts[2], height: parts[3] };
}
}
}
if (dims.width > 0 && dims.height > 0) {
return { width: dims.width, height: dims.height };
}
} catch { /* fall through */ }
return null;
}
/**
* Compute display dimensions that fit within a bounding box while preserving
* the image's aspect ratio. Enforces a minimum height so very wide logos
* remain legible.
*/
function fitToBoundingBox(imgWidth, imgHeight, maxWidth, maxHeight, minHeight) {
const ratio = imgWidth / imgHeight;
// Start at max height, compute width
let h = maxHeight;
let w = h * ratio;
// If too wide, cap width and reduce height
if (w > maxWidth) {
w = maxWidth;
h = w / ratio;
}
// If height fell below minimum, enforce minimum (allow width to exceed max)
if (h < minHeight) {
h = minHeight;
w = h * ratio;
}
return { width: Math.round(w), height: Math.round(h) };
}
/**
* Generate the database logos HTML for README.md
* Only includes databases that have logos defined
* Only includes databases that have logos and homepage URLs.
* Deduplicates by logo filename to match the docs homepage behavior.
* Reads actual image dimensions to preserve aspect ratios.
*/
function generateReadmeLogos(databases) {
// Get databases with logos, sorted alphabetically
// Get databases with logos and homepage URLs, sorted alphabetically,
// deduplicated by logo filename (matches docs homepage logic in index.tsx)
const seenLogos = new Set();
const dbsWithLogos = Object.entries(databases)
.filter(([, db]) => db.documentation?.logo)
.sort(([a], [b]) => a.localeCompare(b));
.filter(([, db]) => db.documentation?.logo && db.documentation?.homepage_url)
.sort(([a], [b]) => a.localeCompare(b))
.filter(([, db]) => {
const logo = db.documentation.logo;
if (seenLogos.has(logo)) return false;
seenLogos.add(logo);
return true;
});
if (dbsWithLogos.length === 0) {
return '';
}
// Generate HTML img tags
const MAX_WIDTH = 150;
const MAX_HEIGHT = 40;
const MIN_HEIGHT = 24;
const DOCS_BASE = 'https://superset.apache.org/docs/databases/supported';
// Generate linked logo tags with aspect-ratio-preserving dimensions
const logoTags = dbsWithLogos.map(([name, db]) => {
const logo = db.documentation.logo;
const alt = name.toLowerCase().replace(/\s+/g, '-');
// Use docs site URL for logos
return ` <img src="https://superset.apache.org/img/databases/${logo}" alt="${alt}" border="0" width="80" height="40" class="database-logo" />`;
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
const imgPath = path.join(IMAGES_DIR, logo);
const dims = getImageDimensions(imgPath);
let sizeAttrs;
if (dims) {
const { width, height } = fitToBoundingBox(dims.width, dims.height, MAX_WIDTH, MAX_HEIGHT, MIN_HEIGHT);
sizeAttrs = `width="${width}" height="${height}"`;
} else {
console.warn(` Could not read dimensions for ${logo}, using height-only fallback`);
sizeAttrs = `height="${MAX_HEIGHT}"`;
}
const img = `<img src="docs/static/img/databases/${logo}" alt="${name}" ${sizeAttrs} />`;
return ` <a href="${DOCS_BASE}/${slug}" title="${name}">${img}</a>`;
});
// Use &nbsp; between logos for spacing (GitHub strips style/class attributes)
return `<p align="center">
${logoTags.join('\n')}
${logoTags.join(' &nbsp;\n')}
</p>`;
}
@@ -675,6 +758,78 @@ function updateReadme(databases) {
return false;
}
/**
* Extract custom_errors from engine specs for troubleshooting documentation
* Returns a map of module names to their custom errors
*/
function extractCustomErrors() {
console.log('Extracting custom_errors from engine specs...');
try {
const scriptPath = path.join(__dirname, 'extract_custom_errors.py');
const result = spawnSync('python3', [scriptPath], {
cwd: ROOT_DIR,
encoding: 'utf-8',
timeout: 30000,
maxBuffer: 10 * 1024 * 1024,
});
if (result.error) {
throw result.error;
}
if (result.status !== 0) {
throw new Error(result.stderr || 'Python script failed');
}
const customErrors = JSON.parse(result.stdout);
const moduleCount = Object.keys(customErrors).length;
const errorCount = Object.values(customErrors).reduce((sum, classes) =>
sum + Object.values(classes).reduce((s, errs) => s + errs.length, 0), 0);
console.log(` Found ${errorCount} custom errors across ${moduleCount} modules`);
return customErrors;
} catch (err) {
console.log(' Could not extract custom_errors:', err.message);
return null;
}
}
/**
* Merge custom_errors into database documentation
* Maps by module name since that's how both datasets are keyed
*/
function mergeCustomErrors(databases, customErrors) {
if (!customErrors) return;
let mergedCount = 0;
for (const [, db] of Object.entries(databases)) {
if (!db.module) continue;
// Normalize module name: Flask mode uses full path (superset.db_engine_specs.postgres),
// but customErrors is keyed by file stem (postgres)
const moduleName = db.module.split('.').pop();
if (!customErrors[moduleName]) continue;
// Get all errors from all classes in this module
const moduleErrors = customErrors[moduleName];
const allErrors = [];
for (const classErrors of Object.values(moduleErrors)) {
allErrors.push(...classErrors);
}
if (allErrors.length > 0) {
// Add to documentation
db.documentation = db.documentation || {};
db.documentation.custom_errors = allErrors;
mergedCount++;
}
}
if (mergedCount > 0) {
console.log(` Merged custom_errors into ${mergedCount} database docs`);
}
}
/**
* Load existing database data if available
*/
@@ -768,6 +923,10 @@ async function main() {
databases = mergeWithExistingDiagnostics(databases, existingData);
}
// Extract and merge custom_errors for troubleshooting documentation
const customErrors = extractCustomErrors();
mergeCustomErrors(databases, customErrors);
// Build statistics
const statistics = buildStatistics(databases);

File diff suppressed because it is too large Load Diff

View File

@@ -110,9 +110,26 @@ const sidebars = {
'testing/frontend-testing',
'testing/backend-testing',
'testing/e2e-testing',
'testing/storybook',
'testing/ci-cd',
],
},
{
type: 'category',
label: 'UI Components',
collapsed: true,
items: [
{
type: 'autogenerated',
dirName: 'components',
},
],
},
{
type: 'link',
label: 'API Reference',
href: '/docs/api',
},
],
};

View File

@@ -107,9 +107,21 @@ const sidebars = {
id: 'faq',
},
{
type: 'doc',
label: 'API',
id: 'api',
type: 'category',
label: 'API Reference',
link: {
type: 'doc',
id: 'api',
},
items: (() => {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require('./docs/api/sidebar.js');
} catch {
// Generated by `yarn generate:api-docs`; empty until then
return [];
}
})(),
},
],
};

View File

@@ -39,11 +39,12 @@ const StyledBlurredSection = styled('section')`
interface BlurredSectionProps {
children: ReactNode;
id?: string;
}
const BlurredSection = ({ children }: BlurredSectionProps) => {
const BlurredSection = ({ children, id }: BlurredSectionProps) => {
return (
<StyledBlurredSection>
<StyledBlurredSection id={id}>
{children}
<img className="blur" src="/img/community/blur.png" alt="Blur" />
</StyledBlurredSection>

View 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>
);
}

View File

@@ -18,33 +18,245 @@
*/
import React from 'react';
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
import BrowserOnly from '@docusaurus/BrowserOnly';
// A simple component to display a story example
export function StoryExample({ component: Component, props = {} }) {
// Lazy-loaded component registry - populated on first use in browser
let componentRegistry = null;
let SupersetProviders = null;
function getComponentRegistry() {
if (typeof window === 'undefined') {
return {}; // SSR - return empty
}
if (componentRegistry !== null) {
return componentRegistry; // Already loaded
}
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const antd = require('antd');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const SupersetComponents = require('@superset/components');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const CoreUI = require('@apache-superset/core/ui');
// Build component registry with antd as base fallback layer.
// Some Superset components (e.g., Typography) use styled-components that may
// fail to initialize in the docs build. Antd originals serve as fallbacks.
componentRegistry = { ...antd, ...SupersetComponents, ...CoreUI };
return componentRegistry;
} catch (error) {
console.error('[StorybookWrapper] Failed to load components:', error);
componentRegistry = {};
return componentRegistry;
}
}
function getProviders() {
if (typeof window === 'undefined') {
return ({ children }) => children; // SSR
}
if (SupersetProviders !== null) {
return SupersetProviders;
}
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { themeObject } = require('@apache-superset/core/ui');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { App, ConfigProvider } = require('antd');
// Configure Ant Design to render portals (tooltips, dropdowns, etc.)
// inside the closest .storybook-example container instead of document.body
// This fixes positioning issues in the docs pages
const getPopupContainer = (triggerNode) => {
// Find the closest .storybook-example container
const container = triggerNode?.closest?.('.storybook-example');
return container || document.body;
};
SupersetProviders = ({ children }) => (
<themeObject.SupersetThemeProvider>
<ConfigProvider
getPopupContainer={getPopupContainer}
getTargetContainer={() => document.body}
>
<App>{children}</App>
</ConfigProvider>
</themeObject.SupersetThemeProvider>
);
return SupersetProviders;
} catch (error) {
console.error('[StorybookWrapper] Failed to load providers:', error);
return ({ children }) => children;
}
}
// Check if a value is a valid React component (function, forwardRef, memo, etc.)
function isReactComponent(value) {
if (!value) return false;
// Function/class components
if (typeof value === 'function') return true;
// forwardRef, memo, lazy — React wraps these as objects with $$typeof
if (typeof value === 'object' && value.$$typeof) return true;
return false;
}
// Resolve component from string name or React component
// Supports dot notation for nested components (e.g., 'Icons.InfoCircleOutlined')
function resolveComponent(component) {
if (!component) return null;
// If already a component (function/class/forwardRef), return as-is
if (isReactComponent(component)) return component;
// If string, look up in registry
if (typeof component === 'string') {
const registry = getComponentRegistry();
// Handle dot notation (e.g., 'Icons.InfoCircleOutlined')
if (component.includes('.')) {
const parts = component.split('.');
let current = registry[parts[0]];
for (let i = 1; i < parts.length && current; i++) {
current = current[parts[i]];
}
return isReactComponent(current) ? current : null;
}
return registry[component] || null;
}
return null;
}
// Loading placeholder for SSR
function LoadingPlaceholder() {
return (
<ThemeProvider theme={supersetTheme}>
<div
className="storybook-example"
style={{
border: '1px solid #e8e8e8',
borderRadius: '4px',
padding: '20px',
marginBottom: '20px',
}}
>
{Component && <Component {...props} />}
</div>
</ThemeProvider>
<div
style={{
border: '1px solid #e8e8e8',
borderRadius: '4px',
padding: '20px',
marginBottom: '20px',
minHeight: '100px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#999',
}}
>
Loading component...
</div>
);
}
// A simple component to display a story with controls
export function StoryWithControls({
component: Component,
props = {},
controls = [],
}) {
// A simple component to display a story example
export function StoryExample({ component, props = {} }) {
return (
<BrowserOnly fallback={<LoadingPlaceholder />}>
{() => {
const Component = resolveComponent(component);
const Providers = getProviders();
const { children, restProps } = extractChildren(props);
return (
<Providers>
<div
className="storybook-example"
style={{
border: '1px solid #e8e8e8',
borderRadius: '4px',
padding: '20px',
marginBottom: '20px',
position: 'relative', // Required for portal positioning
}}
>
{Component ? (
<Component {...restProps}>{children}</Component>
) : (
<div style={{ color: '#999' }}>
Component &quot;{String(component)}&quot; not found
</div>
)}
</div>
</Providers>
);
}}
</BrowserOnly>
);
}
// Props that should be rendered as children rather than passed as props
const CHILDREN_PROP_NAMES = ['label', 'children', 'text', 'content'];
// Extract children from props based on common conventions
function extractChildren(props) {
for (const propName of CHILDREN_PROP_NAMES) {
if (props[propName] !== undefined && props[propName] !== null && props[propName] !== '') {
const { [propName]: childContent, ...restProps } = props;
return { children: childContent, restProps };
}
}
return { children: null, restProps: props };
}
// Generate sample children for layout components
// Supports:
// - Array of strings: ['Item 1', 'Item 2'] - renders as styled divs
// - Array of component descriptors: [{ component: 'Button', props: { children: 'Click' } }]
// - Number: 3 - generates that many sample items
// - String: 'content' - renders as literal content
function generateSampleChildren(sampleChildren, sampleChildrenStyle) {
if (!sampleChildren) return null;
// Default style if none provided (minimal, just enough to see items)
const itemStyle = sampleChildrenStyle || {};
// If it's an array, check if items are component descriptors or strings
if (Array.isArray(sampleChildren)) {
return sampleChildren.map((item, i) => {
// Component descriptor: { component: 'Button', props: { ... } }
if (item && typeof item === 'object' && item.component) {
const ChildComponent = resolveComponent(item.component);
if (ChildComponent) {
return <ChildComponent key={i} {...item.props} />;
}
// Fallback if component not found
return <div key={i}>{item.props?.children || `Unknown: ${item.component}`}</div>;
}
// Simple string
return (
<div key={i} style={itemStyle}>
{item}
</div>
);
});
}
// If it's a number, generate that many sample items
if (typeof sampleChildren === 'number') {
return new Array(sampleChildren).fill(null).map((_, i) => (
<div key={i} style={itemStyle}>
Item {i + 1}
</div>
));
}
// If it's a string, treat as literal content
if (typeof sampleChildren === 'string') {
return sampleChildren;
}
return sampleChildren;
}
// Inner component for StoryWithControls (browser-only)
// renderComponent allows overriding which component to actually render (useful when the named
// component is a namespace object like Icons, not a React component)
// triggerProp: for components like Modal that need a trigger, specify the boolean prop that controls visibility
function StoryWithControlsInner({ component, renderComponent, props, controls, sampleChildren, sampleChildrenStyle, triggerProp, onHideProp }) {
// Use renderComponent if provided, otherwise use the main component name
const componentToRender = renderComponent || component;
const Component = resolveComponent(componentToRender);
const Providers = getProviders();
const [stateProps, setStateProps] = React.useState(props);
const updateProp = (key, value) => {
@@ -54,8 +266,77 @@ export function StoryWithControls({
}));
};
// Extract children from props (label, children, text, content)
// When sampleChildren is explicitly provided, skip extraction so all props
// (like 'content') stay as component props rather than becoming children
const { children: propsChildren, restProps } = sampleChildren
? { children: null, restProps: stateProps }
: extractChildren(stateProps);
// Filter out undefined values so they don't override component defaults
const filteredProps = Object.fromEntries(
Object.entries(restProps).filter(([, v]) => v !== undefined)
);
// Resolve any prop values that are component descriptors
// e.g., { component: 'Button', props: { children: 'Click' } }
// Also resolves descriptors nested inside array items:
// e.g., items: [{ id: 'x', element: { component: 'div', props: { children: 'text' } } }]
Object.keys(filteredProps).forEach(key => {
const value = filteredProps[key];
if (value && typeof value === 'object' && !Array.isArray(value) && value.component) {
const PropComponent = resolveComponent(value.component);
if (PropComponent) {
filteredProps[key] = <PropComponent {...value.props} />;
}
}
if (Array.isArray(value)) {
filteredProps[key] = value.map((item, idx) => {
if (item && typeof item === 'object') {
const resolved = { ...item };
Object.keys(resolved).forEach(field => {
const fieldValue = resolved[field];
if (fieldValue && typeof fieldValue === 'object' && !Array.isArray(fieldValue) && fieldValue.component) {
const FieldComponent = resolveComponent(fieldValue.component);
if (FieldComponent) {
resolved[field] = React.createElement(FieldComponent, { key: `${key}-${idx}`, ...fieldValue.props });
}
}
});
return resolved;
}
return item;
});
}
});
// For List-like components with dataSource but no renderItem, provide a default
if (filteredProps.dataSource && !filteredProps.renderItem) {
const ListItem = resolveComponent('List')?.Item;
filteredProps.renderItem = (item) =>
ListItem
? React.createElement(ListItem, null, String(item))
: React.createElement('div', null, String(item));
}
// Use sample children if provided, otherwise use props children
const children = generateSampleChildren(sampleChildren, sampleChildrenStyle) || propsChildren;
// For components with a trigger (like Modal with show/onHide), add handlers.
// onHideProp supports comma-separated names for components with multiple close
// callbacks (e.g., "onHide,handleSave,onConfirmNavigation").
const triggerProps = {};
if (triggerProp && onHideProp) {
const closeHandler = () => updateProp(triggerProp, false);
onHideProp.split(',').forEach(prop => {
triggerProps[prop.trim()] = closeHandler;
});
}
// Get the Button component for trigger buttons
const ButtonComponent = resolveComponent('Button');
return (
<ThemeProvider theme={supersetTheme}>
<Providers>
<div className="storybook-with-controls">
<div
className="storybook-example"
@@ -64,9 +345,24 @@ export function StoryWithControls({
borderRadius: '4px',
padding: '20px',
marginBottom: '20px',
position: 'relative', // Required for portal positioning
}}
>
{Component && <Component {...stateProps} />}
{Component ? (
<>
{/* Show a trigger button for components like Modal */}
{triggerProp && ButtonComponent && (
<ButtonComponent onClick={() => updateProp(triggerProp, true)}>
Open {component}
</ButtonComponent>
)}
<Component {...filteredProps} {...triggerProps}>{children}</Component>
</>
) : (
<div style={{ color: '#999' }}>
Component &quot;{String(componentToRender)}&quot; not found
</div>
)}
</div>
{controls.length > 0 && (
@@ -87,26 +383,64 @@ export function StoryWithControls({
</label>
{control.type === 'select' ? (
<select
value={stateProps[control.name]}
onChange={e => updateProp(control.name, e.target.value)}
value={stateProps[control.name] ?? ''}
onChange={e => updateProp(control.name, e.target.value || undefined)}
style={{ width: '100%', padding: '5px' }}
>
{control.options.map(option => (
<option value=""> None </option>
{control.options?.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
) : control.type === 'inline-radio' || control.type === 'radio' ? (
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
{control.options?.map(option => (
<label
key={option}
style={{ display: 'flex', alignItems: 'center', gap: '4px' }}
>
<input
type="radio"
name={control.name}
value={option}
checked={stateProps[control.name] === option}
onChange={e => updateProp(control.name, e.target.value)}
/>
{option}
</label>
))}
</div>
) : control.type === 'boolean' ? (
<input
type="checkbox"
checked={stateProps[control.name]}
onChange={e => updateProp(control.name, e.target.checked)}
/>
) : control.type === 'number' ? (
<input
type="number"
value={stateProps[control.name]}
onChange={e => updateProp(control.name, Number(e.target.value))}
style={{ width: '100%', padding: '5px' }}
/>
) : control.type === 'color' ? (
<input
type="color"
value={stateProps[control.name] || '#000000'}
onChange={e => updateProp(control.name, e.target.value)}
style={{
width: '50px',
height: '30px',
padding: '2px',
cursor: 'pointer',
}}
/>
) : (
<input
type="text"
value={stateProps[control.name]}
value={stateProps[control.name] ?? ''}
onChange={e => updateProp(control.name, e.target.value)}
style={{ width: '100%', padding: '5px' }}
/>
@@ -116,6 +450,81 @@ export function StoryWithControls({
</div>
)}
</div>
</ThemeProvider>
</Providers>
);
}
// A simple component to display a story with controls
// renderComponent: optional override for which component to render (e.g., 'Icons.InfoCircleOutlined' when component='Icons')
// triggerProp/onHideProp: for components like Modal that need a button to open (e.g., triggerProp="show", onHideProp="onHide")
export function StoryWithControls({ component: Component, renderComponent, props = {}, controls = [], sampleChildren, sampleChildrenStyle, triggerProp, onHideProp }) {
return (
<BrowserOnly fallback={<LoadingPlaceholder />}>
{() => (
<StoryWithControlsInner
component={Component}
renderComponent={renderComponent}
props={props}
controls={controls}
sampleChildren={sampleChildren}
sampleChildrenStyle={sampleChildrenStyle}
triggerProp={triggerProp}
onHideProp={onHideProp}
/>
)}
</BrowserOnly>
);
}
// Inner component for ComponentGallery (browser-only)
function ComponentGalleryInner({ component, sizes, styles, sizeProp, styleProp }) {
const Component = resolveComponent(component);
const Providers = getProviders();
if (!Component) {
return (
<div style={{ color: '#999' }}>
Component &quot;{String(component)}&quot; not found
</div>
);
}
return (
<Providers>
<div className="component-gallery">
{sizes.map(size => (
<div key={size} style={{ marginBottom: 40 }}>
<h4 style={{ marginBottom: 16, color: '#666' }}>{size}</h4>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '12px', alignItems: 'center' }}>
{styles.map(style => (
<Component
key={`${style}_${size}`}
{...{ [sizeProp]: size, [styleProp]: style }}
>
{style}
</Component>
))}
</div>
</div>
))}
</div>
</Providers>
);
}
// A component to display a gallery of all variants (sizes x styles)
export function ComponentGallery({ component, sizes = [], styles = [], sizeProp = 'size', styleProp = 'variant' }) {
return (
<BrowserOnly fallback={<LoadingPlaceholder />}>
{() => (
<ComponentGalleryInner
component={component}
sizes={sizes}
styles={styles}
sizeProp={sizeProp}
styleProp={styleProp}
/>
)}
</BrowserOnly>
);
}

View 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>
);
}

View File

@@ -26,6 +26,7 @@ import {
KeyOutlined,
SearchOutlined,
LinkOutlined,
BugOutlined,
} from '@ant-design/icons';
import type { DatabaseData, DatabaseInfo, TimeGrains } from './types';
@@ -44,6 +45,8 @@ interface TableEntry {
hasDrivers: boolean;
hasAuthMethods: boolean;
hasConnectionString: boolean;
hasCustomErrors: boolean;
customErrorCount: number;
joins?: boolean;
subqueries?: boolean;
supports_dynamic_schema?: boolean;
@@ -223,6 +226,8 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
db.documentation?.connection_string ||
(db.documentation?.drivers?.length ?? 0) > 0
),
hasCustomErrors: (db.documentation?.custom_errors?.length ?? 0) > 0,
customErrorCount: db.documentation?.custom_errors?.length ?? 0,
isCompatible: false,
});
@@ -246,6 +251,8 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
hasDrivers: false,
hasAuthMethods: false,
hasConnectionString: Boolean(compat.connection_string),
hasCustomErrors: false,
customErrorCount: 0,
joins: db.joins,
subqueries: db.subqueries,
supports_dynamic_schema: db.supports_dynamic_schema,
@@ -457,7 +464,7 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
{
title: 'Documentation',
key: 'docs',
width: 150,
width: 180,
render: (_: unknown, record: TableEntry) => (
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
{record.hasConnectionString && (
@@ -475,6 +482,13 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
Auth
</Tag>
)}
{record.hasCustomErrors && (
<Tooltip title={`${record.customErrorCount} troubleshooting tips`}>
<Tag icon={<BugOutlined />} color="volcano">
Errors
</Tag>
</Tooltip>
)}
</div>
),
},

View File

@@ -39,6 +39,7 @@ import {
BookOutlined,
EditOutlined,
GithubOutlined,
BugOutlined,
} from '@ant-design/icons';
import type { DatabaseInfo } from './types';
@@ -59,8 +60,6 @@ const CodeBlock: React.FC<{ children: React.ReactNode }> = ({ children }) => (
);
const { Title, Paragraph, Text } = Typography;
const { Panel } = Collapse;
const { TabPane } = Tabs;
interface DatabasePageProps {
database: DatabaseInfo;
@@ -111,21 +110,20 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
return (
<Card title="Drivers" style={{ marginBottom: 16 }}>
<Tabs>
{docs.drivers.map((driver, idx) => (
<TabPane
tab={
<span>
{driver.name}
{driver.is_recommended && (
<Tag color="green" style={{ marginLeft: 8 }}>
Recommended
</Tag>
)}
</span>
}
key={idx}
>
<Tabs
items={docs.drivers.map((driver, idx) => ({
key: String(idx),
label: (
<span>
{driver.name}
{driver.is_recommended && (
<Tag color="green" style={{ marginLeft: 8 }}>
Recommended
</Tag>
)}
</span>
),
children: (
<Space direction="vertical" style={{ width: '100%' }}>
{driver.pypi_package && (
<div>
@@ -144,9 +142,9 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
</a>
)}
</Space>
</TabPane>
))}
</Tabs>
),
}))}
/>
</Card>
);
};
@@ -164,46 +162,51 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
}
style={{ marginBottom: 16 }}
>
<Collapse accordion>
{docs.authentication_methods.map((auth, idx) => (
<Panel header={auth.name} key={idx}>
{auth.description && <Paragraph>{auth.description}</Paragraph>}
{auth.requirements && (
<Alert
message="Requirements"
description={auth.requirements}
type="warning"
showIcon
style={{ marginBottom: 16 }}
/>
)}
{auth.connection_string &&
renderConnectionString(
auth.connection_string,
'Connection String'
<Collapse
accordion
items={docs.authentication_methods.map((auth, idx) => ({
key: String(idx),
label: auth.name,
children: (
<>
{auth.description && <Paragraph>{auth.description}</Paragraph>}
{auth.requirements && (
<Alert
message="Requirements"
description={auth.requirements}
type="warning"
showIcon
style={{ marginBottom: 16 }}
/>
)}
{auth.secure_extra && (
<div>
<Text strong>Secure Extra Configuration:</Text>
<CodeBlock>
{JSON.stringify(auth.secure_extra, null, 2)}
</CodeBlock>
</div>
)}
{auth.engine_parameters && (
<div>
<Text strong>Engine Parameters:</Text>
<CodeBlock>
{JSON.stringify(auth.engine_parameters, null, 2)}
</CodeBlock>
</div>
)}
{auth.notes && (
<Alert message={auth.notes} type="info" showIcon />
)}
</Panel>
))}
</Collapse>
{auth.connection_string &&
renderConnectionString(
auth.connection_string,
'Connection String',
)}
{auth.secure_extra && (
<div>
<Text strong>Secure Extra Configuration:</Text>
<CodeBlock>
{JSON.stringify(auth.secure_extra, null, 2)}
</CodeBlock>
</div>
)}
{auth.engine_parameters && (
<div>
<Text strong>Engine Parameters:</Text>
<CodeBlock>
{JSON.stringify(auth.engine_parameters, null, 2)}
</CodeBlock>
</div>
)}
{auth.notes && (
<Alert message={auth.notes} type="info" showIcon />
)}
</>
),
}))}
/>
</Card>
);
};
@@ -221,23 +224,27 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
}
style={{ marginBottom: 16 }}
>
<Collapse>
{docs.engine_parameters.map((param, idx) => (
<Panel header={param.name} key={idx}>
{param.description && <Paragraph>{param.description}</Paragraph>}
{param.json && (
<CodeBlock>
{JSON.stringify(param.json, null, 2)}
</CodeBlock>
)}
{param.docs_url && (
<a href={param.docs_url} target="_blank" rel="noreferrer">
<LinkOutlined /> Learn more
</a>
)}
</Panel>
))}
</Collapse>
<Collapse
items={docs.engine_parameters.map((param, idx) => ({
key: String(idx),
label: param.name,
children: (
<>
{param.description && (
<Paragraph>{param.description}</Paragraph>
)}
{param.json && (
<CodeBlock>{JSON.stringify(param.json, null, 2)}</CodeBlock>
)}
{param.docs_url && (
<a href={param.docs_url} target="_blank" rel="noreferrer">
<LinkOutlined /> Learn more
</a>
)}
</>
),
}))}
/>
</Card>
);
};
@@ -246,75 +253,81 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
const renderCompatibleDatabases = () => {
if (!docs?.compatible_databases?.length) return null;
// Create array of all panel keys to expand by default
const allPanelKeys = docs.compatible_databases.map((_, idx) => idx);
// Create array of all item keys to expand by default
const allItemKeys = docs.compatible_databases.map((_, idx) => String(idx));
return (
<Card title="Compatible Databases" style={{ marginBottom: 16 }}>
<Paragraph>
The following databases are compatible with the {name} driver:
</Paragraph>
<Collapse defaultActiveKey={allPanelKeys}>
{docs.compatible_databases.map((compat, idx) => (
<Panel
header={
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
{compat.logo && (
<img
src={`/img/databases/${compat.logo}`}
alt={compat.name}
style={{
width: 28,
height: 28,
objectFit: 'contain',
}}
/>
)}
<span>{compat.name}</span>
</div>
}
key={idx}
>
{compat.description && (
<Paragraph>{compat.description}</Paragraph>
)}
{compat.connection_string &&
renderConnectionString(compat.connection_string)}
{compat.parameters && (
<div>
<Text strong>Parameters:</Text>
<Table
dataSource={Object.entries(compat.parameters).map(
([key, value]) => ({
key,
parameter: key,
description: value,
})
)}
columns={[
{ title: 'Parameter', dataIndex: 'parameter', key: 'p' },
{
title: 'Description',
dataIndex: 'description',
key: 'd',
},
]}
pagination={false}
size="small"
<Collapse
defaultActiveKey={allItemKeys}
items={docs.compatible_databases.map((compat, idx) => ({
key: String(idx),
label: (
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
{compat.logo && (
<img
src={`/img/databases/${compat.logo}`}
alt={compat.name}
style={{
width: 28,
height: 28,
objectFit: 'contain',
}}
/>
</div>
)}
{compat.notes && (
<Alert
message={compat.notes}
type="info"
showIcon
style={{ marginTop: 16 }}
/>
)}
</Panel>
))}
</Collapse>
)}
<span>{compat.name}</span>
</div>
),
children: (
<>
{compat.description && (
<Paragraph>{compat.description}</Paragraph>
)}
{compat.connection_string &&
renderConnectionString(compat.connection_string)}
{compat.parameters && (
<div>
<Text strong>Parameters:</Text>
<Table
dataSource={Object.entries(compat.parameters).map(
([key, value]) => ({
key,
parameter: key,
description: value,
}),
)}
columns={[
{
title: 'Parameter',
dataIndex: 'parameter',
key: 'p',
},
{
title: 'Description',
dataIndex: 'description',
key: 'd',
},
]}
pagination={false}
size="small"
/>
</div>
)}
{compat.notes && (
<Alert
message={compat.notes}
type="info"
showIcon
style={{ marginTop: 16 }}
/>
)}
</>
),
}))}
/>
</Card>
);
};
@@ -375,7 +388,7 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
'YEAR',
];
const extendedGrains = Object.keys(database.time_grains).filter(
(g) => !commonGrains.includes(g)
g => !commonGrains.includes(g),
);
return (
@@ -383,12 +396,14 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
<div style={{ marginBottom: 16 }}>
<Text strong>Common Time Grains:</Text>
<div style={{ marginTop: 8 }}>
{commonGrains.map((grain) => (
{commonGrains.map(grain => (
<TimeGrainBadge
key={grain}
grain={grain}
supported={Boolean(
database.time_grains[grain as keyof typeof database.time_grains]
database.time_grains[
grain as keyof typeof database.time_grains
],
)}
/>
))}
@@ -398,12 +413,14 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
<div>
<Text strong>Extended Time Grains:</Text>
<div style={{ marginTop: 8 }}>
{extendedGrains.map((grain) => (
{extendedGrains.map(grain => (
<TimeGrainBadge
key={grain}
grain={grain}
supported={Boolean(
database.time_grains[grain as keyof typeof database.time_grains]
database.time_grains[
grain as keyof typeof database.time_grains
],
)}
/>
))}
@@ -414,11 +431,139 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
);
};
// Render troubleshooting / custom errors section
const renderTroubleshooting = () => {
if (!docs?.custom_errors?.length) return null;
// Group errors by category
const errorsByCategory: Record<string, typeof docs.custom_errors> = {};
for (const error of docs.custom_errors) {
const category = error.category || 'General';
if (!errorsByCategory[category]) {
errorsByCategory[category] = [];
}
errorsByCategory[category].push(error);
}
// Define category order for consistent display
const categoryOrder = [
'Authentication',
'Connection',
'Permissions',
'Query',
'Configuration',
'General',
];
const sortedCategories = Object.keys(errorsByCategory).sort((a, b) => {
const aIdx = categoryOrder.indexOf(a);
const bIdx = categoryOrder.indexOf(b);
if (aIdx === -1 && bIdx === -1) return a.localeCompare(b);
if (aIdx === -1) return 1;
if (bIdx === -1) return -1;
return aIdx - bIdx;
});
// Category colors
const categoryColors: Record<string, string> = {
Authentication: 'orange',
Connection: 'red',
Permissions: 'purple',
Query: 'blue',
Configuration: 'cyan',
General: 'default',
};
return (
<Card
title={
<>
<BugOutlined /> Troubleshooting
</>
}
style={{ marginBottom: 16 }}
>
<Paragraph type="secondary">
Common error messages you may encounter when connecting to or querying{' '}
{name}, along with their causes and solutions.
</Paragraph>
<Collapse
accordion
items={sortedCategories.map(category => ({
key: category,
label: (
<span>
<Tag color={categoryColors[category] || 'default'}>
{category}
</Tag>
{errorsByCategory[category].length} error
{errorsByCategory[category].length !== 1 ? 's' : ''}
</span>
),
children: (
<>
{errorsByCategory[category].map((error, idx) => (
<div
key={idx}
style={{
marginBottom:
idx < errorsByCategory[category].length - 1 ? 16 : 0,
paddingBottom:
idx < errorsByCategory[category].length - 1 ? 16 : 0,
borderBottom:
idx < errorsByCategory[category].length - 1
? '1px solid var(--ifm-color-emphasis-200)'
: 'none',
}}
>
<div style={{ marginBottom: 8 }}>
<Text strong>
{error.description || error.error_type}
</Text>
</div>
<Alert
message={error.message_template}
type="error"
style={{ marginBottom: 8 }}
/>
{error.invalid_fields &&
error.invalid_fields.length > 0 && (
<div style={{ marginBottom: 8 }}>
<Text type="secondary">Check these fields: </Text>
{error.invalid_fields.map(field => (
<Tag key={field} color="warning">
{field}
</Tag>
))}
</div>
)}
{error.issue_codes && error.issue_codes.length > 0 && (
<div>
<Text type="secondary">Related issue codes: </Text>
{error.issue_codes.map(code => (
<Tag key={code}>
<a
href={`/docs/using-superset/issue-codes#issue-${code}`}
style={{ color: 'inherit' }}
>
Issue {code}
</a>
</Tag>
))}
</div>
)}
</div>
))}
</>
),
}))}
/>
</Card>
);
};
return (
<div
className="database-page"
id={name.toLowerCase().replace(/\s+/g, '-')}
>
<div className="database-page" id={name.toLowerCase().replace(/\s+/g, '-')}>
<div style={{ marginBottom: 16 }}>
{docs?.logo && (
<img
@@ -431,7 +576,9 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
}}
/>
)}
<Title level={1} style={{ margin: 0 }}>{name}</Title>
<Title level={1} style={{ margin: 0 }}>
{name}
</Title>
{docs?.homepage_url && (
<a
href={docs.homepage_url}
@@ -479,7 +626,7 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
{docs.pypi_packages?.length > 0 && (
<div style={{ marginBottom: 16 }}>
<Text strong>Required packages: </Text>
{docs.pypi_packages.map((pkg) => (
{docs.pypi_packages.map(pkg => (
<Tag key={pkg} color="blue">
{pkg}
</Tag>
@@ -511,7 +658,7 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
key,
parameter: key,
description: value,
})
}),
)}
columns={[
{ title: 'Parameter', dataIndex: 'parameter', key: 'p' },
@@ -537,7 +684,7 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
<div key={idx}>
{renderConnectionString(
example.connection_string,
example.description
example.description,
)}
</div>
))}
@@ -556,6 +703,9 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
{/* Time Grains */}
{renderTimeGrains()}
{/* Troubleshooting / Custom Errors */}
{renderTroubleshooting()}
{/* Compatible Databases */}
{renderCompatibleDatabases()}
@@ -587,7 +737,11 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
</a>
)}
{docs.sqlalchemy_docs_url && (
<a href={docs.sqlalchemy_docs_url} target="_blank" rel="noreferrer">
<a
href={docs.sqlalchemy_docs_url}
target="_blank"
rel="noreferrer"
>
<LinkOutlined /> SQLAlchemy Dialect Documentation
</a>
)}

View File

@@ -86,6 +86,17 @@ export interface CompatibleDatabase {
docs_url?: string;
}
export interface CustomError {
error_type: string; // e.g., "CONNECTION_INVALID_USERNAME_ERROR"
message_template: string; // e.g., 'The username "%(username)s" does not exist.'
regex_pattern?: string; // The regex pattern that matches this error (optional, for reference)
regex_name?: string; // The name of the regex constant (e.g., "CONNECTION_INVALID_USERNAME_REGEX")
invalid_fields?: string[]; // Fields that are invalid, e.g., ["username", "password"]
issue_codes?: number[]; // Related issue codes from ISSUE_CODES mapping
category?: string; // Error category: "Authentication", "Connection", "Query", etc.
description?: string; // Human-readable short description of the error type
}
export interface DatabaseDocumentation {
description?: string;
logo?: string;
@@ -111,6 +122,7 @@ export interface DatabaseDocumentation {
sqlalchemy_docs_url?: string;
advanced_features?: Record<string, string>;
compatible_databases?: CompatibleDatabase[];
custom_errors?: CustomError[]; // Database-specific error messages and troubleshooting info
}
export interface TimeGrains {

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,6 @@
*/
import { useState } from 'react';
import styled from '@emotion/styled';
import { List } from 'antd';
import Layout from '@theme/Layout';
import { mq } from '../utils';
import SectionHeader from '../components/SectionHeader';
@@ -92,8 +91,12 @@ const StyledJoinCommunity = styled('section')`
max-width: 540px;
margin: 0 auto;
padding: 40px 20px 20px 35px;
list-style: none;
}
.item {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 0;
border: 0;
}
@@ -189,39 +192,33 @@ const Community = () => {
/>
</BlurredSection>
<StyledJoinCommunity>
<List
className="list"
itemLayout="horizontal"
dataSource={communityLinks}
renderItem={({ url, title, description, image, ariaLabel }) => (
<List.Item className="item">
<List.Item.Meta
avatar={
<a
className="title"
href={url}
target="_blank"
rel="noreferrer"
aria-label={ariaLabel}
>
<img className="icon" src={`/img/community/${image}`} />
</a>
}
title={
<ul className="list">
{communityLinks.map(
({ url, title, description, image, ariaLabel }) => (
<li className="item" key={title}>
<a
className="avatar"
href={url}
target="_blank"
rel="noreferrer"
aria-label={ariaLabel}
>
<img className="icon" src={`/img/community/${image}`} />
</a>
<div>
<a href={url} target="_blank" rel="noreferrer">
<p className="title" style={{ marginBottom: 0 }}>
{title}
</p>
</a>
}
description={<p className="description">{description}</p>}
aria-label="Community link"
/>
</List.Item>
<p className="description">{description}</p>
</div>
</li>
),
)}
/>
</ul>
</StyledJoinCommunity>
<BlurredSection>
<BlurredSection id="superset-community-calendar">
<SectionHeader
level="h2"
title="Superset Community Calendar"

View File

@@ -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
@@ -109,13 +109,10 @@ const StyledMain = styled('main')`
const StyledTitleContainer = styled('div')`
position: relative;
padding: 130px 20px 0;
margin-bottom: 160px;
padding: 130px 20px 20px;
margin-bottom: 0;
background-image: url('/img/grid-background.jpg');
background-size: cover;
${mq[1]} {
margin-bottom: 100px;
}
.info-container {
position: relative;
z-index: 4;
@@ -798,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

View File

@@ -0,0 +1,118 @@
/**
* 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.
*/
// Null module shim for packages not available in the docs build.
// These are transitive dependencies of superset-frontend components that exist
// in the barrel file but are never rendered on the docs site.
// webpack needs these to resolve at build time even though the code paths
// that use them are never executed at runtime.
//
// This shim uses a recursive Proxy to handle nested property access chains:
// import ace from 'ace-builds'; ace.config.set(...) → works (returns proxy)
// import { useResizeDetector } from 'react-resize-detector' → returns noop hook
// import ReactAce from 'react-ace' → returns NullComponent
const NullComponent = () => null;
// For hooks that return objects/arrays
const useNoop = () => ({});
// Mock for useResizeDetector - returns { ref, width, height } where ref.current exists
const useResizeDetectorMock = () => ({
ref: { current: null },
width: 0,
height: 0,
});
/**
* Creates a recursive proxy that handles any depth of property access.
* This allows patterns like ace.config.set() or ace.config.setModuleUrl() to work.
*
* The proxy is both callable (returns undefined) and accessible (returns another proxy).
*/
function createDeepProxy() {
const handler = {
// Handle property access - return another proxy for chaining
get(target, prop) {
// Standard module properties
if (prop === 'default') return createDeepProxy();
if (prop === '__esModule') return true;
// Symbol properties (used by JS internals)
if (typeof prop === 'symbol') {
if (prop === Symbol.toPrimitive) return () => '';
if (prop === Symbol.toStringTag) return 'NullModule';
if (prop === Symbol.iterator) return undefined;
return undefined;
}
// React-specific properties
if (prop === '$$typeof') return undefined;
if (prop === 'propTypes') return undefined;
if (prop === 'displayName') return 'NullComponent';
// Specific hook mocks for known hooks that need proper return values
if (prop === 'useResizeDetector') {
return useResizeDetectorMock;
}
// Common hook names return useNoop for better compatibility
if (typeof prop === 'string' && prop.startsWith('use')) {
return useNoop;
}
// Return another proxy to allow further chaining (ace.config.set)
return createDeepProxy();
},
// Handle function calls - return undefined (safe default)
apply() {
return undefined;
},
// Handle new ClassName() - return an empty object
construct() {
return {};
},
};
// Create a proxy over a function so it's both callable and has properties
return new Proxy(function NullModule() {}, handler);
}
// Create the main module export as a deep proxy
const nullModule = createDeepProxy();
// Support both CommonJS and ES module patterns
module.exports = nullModule;
module.exports.default = createDeepProxy();
module.exports.__esModule = true;
// Named exports for common patterns (webpack may inline these)
module.exports.useResizeDetector = useResizeDetectorMock;
module.exports.withResizeDetector = createDeepProxy();
module.exports.Resizable = NullComponent;
module.exports.ResizableBox = NullComponent;
module.exports.FixedSizeList = NullComponent;
module.exports.VariableSizeList = NullComponent;
// ace-builds specific exports that CodeEditor uses
module.exports.config = createDeepProxy();
module.exports.require = createDeepProxy();
module.exports.edit = createDeepProxy();

54
docs/src/shims/react-table.js vendored Normal file
View File

@@ -0,0 +1,54 @@
/**
* 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.
*/
// Shim for react-table to handle CommonJS to ES module interop
// react-table v7 is CommonJS, but Superset components import it with ES module syntax
// Use relative path to avoid circular dependency since webpack aliases 'react-table' to this file
// eslint-disable-next-line @typescript-eslint/no-require-imports -- CJS interop shim for react-table v7
const reactTable = require('../../node_modules/react-table');
// Re-export all named exports
export const {
useTable,
useFilters,
useSortBy,
usePagination,
useGlobalFilter,
useRowSelect,
useRowState,
useColumnOrder,
useExpanded,
useGroupBy,
useResizeColumns,
useBlockLayout,
useAbsoluteLayout,
useFlexLayout,
actions,
defaultColumn,
makePropGetter,
reduceHooks,
loopHooks,
ensurePluginOrder,
functionalUpdate,
useGetLatest,
safeUseLayoutEffect,
} = reactTable;
// Default export
export default reactTable;

View File

@@ -187,3 +187,270 @@ ul.dropdown__menu svg {
[data-theme='dark'] .ant-collapse-header {
color: var(--ifm-font-base-color);
}
/* Hide the non-functional "Send API Request" button and Response block in API docs */
/* The interactive API testing doesn't work due to CORS restrictions */
.openapi-explorer__request-btn {
display: none !important;
}
.openapi-explorer__response-container {
display: none !important;
}
/* API Method Badge Colors - Swagger-style color coding */
/* These override Infima badge classes used by docusaurus-openapi-docs */
/* GET - Blue (badge--primary) */
.openapi__method-endpoint .badge--primary {
background-color: #61affe !important;
border-color: #61affe !important;
}
/* POST - Green (badge--success) */
.openapi__method-endpoint .badge--success {
background-color: #49cc90 !important;
border-color: #49cc90 !important;
}
/* PUT - Info/Cyan -> Orange (badge--info) */
.openapi__method-endpoint .badge--info {
background-color: #fca130 !important;
border-color: #fca130 !important;
}
/* PATCH - Warning/Yellow -> Teal (badge--warning) */
.openapi__method-endpoint .badge--warning {
background-color: #50e3c2 !important;
border-color: #50e3c2 !important;
color: #1b1b1d !important;
}
/* DELETE - Red (badge--danger) */
.openapi__method-endpoint .badge--danger {
background-color: #f93e3e !important;
border-color: #f93e3e !important;
}
/* Sidebar method badges - colored dots before endpoint names */
/* The method classes (get, post, etc.) are on the <li> (menu__list-item),
so we target the <a> (menu__link) inside using descendant selector */
.menu__list-item.api-method > .menu__link::before {
content: '';
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 8px;
flex-shrink: 0;
}
.menu__list-item.get.api-method > .menu__link::before {
background-color: #61affe;
}
.menu__list-item.post.api-method > .menu__link::before {
background-color: #49cc90;
}
.menu__list-item.put.api-method > .menu__link::before {
background-color: #fca130;
}
.menu__list-item.patch.api-method > .menu__link::before {
background-color: #50e3c2;
}
.menu__list-item.delete.api-method > .menu__link::before {
background-color: #f93e3e;
}
/* ============================================
Component Example Isolation
Prevents Docusaurus/Infima styles from bleeding into Superset components
============================================ */
/* Reset link styles inside component examples */
.storybook-example a {
color: inherit;
text-decoration: none;
font-weight: inherit;
line-height: inherit;
vertical-align: inherit;
}
.storybook-example a:hover {
color: inherit;
text-decoration: none;
}
/* Reset list styles */
.storybook-example ul,
.storybook-example ol {
margin: 0;
padding: 0;
list-style: none;
}
/* Override Infima's .markdown li + li margin */
.storybook-example li + li,
.markdown .storybook-example li + li {
margin-top: 0;
}
/* Reset heading styles */
.storybook-example h1,
.storybook-example h2,
.storybook-example h3,
.storybook-example h4,
.storybook-example h5,
.storybook-example h6 {
margin: 0;
font-size: inherit;
font-weight: inherit;
}
/* Reset paragraph margins */
.storybook-example p {
margin: 0;
}
/* Reset table margins - Infima applies margin-bottom via --ifm-spacing-vertical */
.storybook-example table {
margin: 0;
display: table;
}
/* Ensure Ant Design components render correctly */
.storybook-example .ant-breadcrumb {
line-height: 1.5715;
}
.storybook-example .ant-breadcrumb a {
color: rgba(0, 0, 0, 0.45);
}
.storybook-example .ant-breadcrumb a:hover {
color: rgba(0, 0, 0, 0.85);
}
/* ============================================
Ant Design Popup/Portal Isolation
These components render outside .storybook-example via portals
============================================ */
/* DatePicker, TimePicker dropdown panels - reset Infima table styles
Using doubled selectors for higher specificity than Infima's defaults */
.ant-picker-dropdown.ant-picker-dropdown table,
.ant-picker-dropdown.ant-picker-dropdown thead,
.ant-picker-dropdown.ant-picker-dropdown tbody,
.ant-picker-dropdown.ant-picker-dropdown tr,
.ant-picker-dropdown.ant-picker-dropdown th,
.ant-picker-dropdown.ant-picker-dropdown td {
border: none;
background: none;
background-color: transparent;
}
.ant-picker-dropdown.ant-picker-dropdown table {
border-collapse: separate;
border-spacing: 0;
width: 100%;
display: table;
}
/* Override Infima's zebra striping with higher specificity */
.ant-picker-dropdown.ant-picker-dropdown tr:nth-child(2n),
.ant-picker-dropdown.ant-picker-dropdown tbody tr:nth-child(2n) {
background: none;
background-color: transparent;
}
.ant-picker-dropdown.ant-picker-dropdown th,
.ant-picker-dropdown.ant-picker-dropdown td {
padding: 0;
}
/* Select, Dropdown, Popover portals */
.ant-select-dropdown.ant-select-dropdown table,
.ant-select-dropdown.ant-select-dropdown thead,
.ant-select-dropdown.ant-select-dropdown tbody,
.ant-select-dropdown.ant-select-dropdown tr,
.ant-select-dropdown.ant-select-dropdown th,
.ant-select-dropdown.ant-select-dropdown td,
.ant-dropdown.ant-dropdown table,
.ant-dropdown.ant-dropdown thead,
.ant-dropdown.ant-dropdown tbody,
.ant-dropdown.ant-dropdown tr,
.ant-dropdown.ant-dropdown th,
.ant-dropdown.ant-dropdown td,
.ant-popover.ant-popover table,
.ant-popover.ant-popover thead,
.ant-popover.ant-popover tbody,
.ant-popover.ant-popover tr,
.ant-popover.ant-popover th,
.ant-popover.ant-popover td {
border: none;
background: none;
background-color: transparent;
}
.ant-select-dropdown.ant-select-dropdown tr:nth-child(2n),
.ant-dropdown.ant-dropdown tr:nth-child(2n),
.ant-popover.ant-popover tr:nth-child(2n) {
background: none;
background-color: transparent;
}
/* Modal portals */
.ant-modal.ant-modal table,
.ant-modal.ant-modal thead,
.ant-modal.ant-modal tbody,
.ant-modal.ant-modal tr,
.ant-modal.ant-modal th,
.ant-modal.ant-modal td {
border: none;
background: none;
background-color: transparent;
}
.ant-modal.ant-modal tr:nth-child(2n) {
background: none;
background-color: transparent;
}
/* ============================================
Live Code Editor Height Limits
Prevents tall code blocks from dominating the page
============================================ */
/* Limit the code editor height and make it scrollable */
/* Target multiple possible class names used by Docusaurus/react-live */
.playgroundEditor,
[class*="playgroundEditor"],
.live-editor,
[class*="liveEditor"] {
max-height: 350px !important;
overflow: auto !important;
}
/* The actual textarea/code area inside the editor */
.playgroundEditor textarea,
.playgroundEditor pre,
[class*="playgroundEditor"] textarea,
[class*="playgroundEditor"] pre {
max-height: 350px !important;
overflow: auto !important;
}
/* Also limit the preview area for consistency */
.playgroundPreview,
[class*="playgroundPreview"] {
max-height: 400px;
overflow: auto;
}
/* Hide sidebar items with sidebar_class_name: hidden in frontmatter */
.menu__list-item.hidden {
display: none;
}

View File

@@ -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);
@@ -57,13 +56,7 @@ html[data-theme='dark'] .docusaurus-highlight-code-line {
font-weight: 400;
}
/* Hacks to disable Swagger UI's "try it out" interactive mode */
.try-out,
.auth-wrapper,
.information-container {
display: none !important;
}
/* Legacy Swagger UI styles (for versioned docs that still use swagger-ui-react) */
.swagger-ui table td,
.swagger-ui table th,
.swagger-ui table tr {
@@ -87,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 */
@@ -115,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 {
@@ -159,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 {
@@ -192,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 {
@@ -226,18 +222,37 @@ a > span > svg {
font-size: 15px;
}
.footer__applitools {
.footer__ci-services {
background-color: #0d3e49;
color: #e1e1e1;
position: absolute;
top: 0;
left: 0;
width: 100%;
padding: 16px 0;
padding: 12px 0;
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
}
img {
height: 34px;
}
.footer__ci-services span {
font-size: 13px;
opacity: 0.85;
}
.footer__ci-services a {
display: inline-flex;
align-items: center;
transition: opacity 0.2s;
}
.footer__ci-services a:hover {
opacity: 0.8;
}
.footer__ci-services img {
height: 28px;
}
.footer__divider {
@@ -252,7 +267,16 @@ a > span > svg {
}
@media only screen and (max-width: 996px) {
.footer__applitools img {
height: 28px;
.footer__ci-services {
gap: 12px;
padding: 10px 16px;
}
.footer__ci-services span {
font-size: 12px;
}
.footer__ci-services img {
height: 22px;
}
}

10
docs/src/theme.d.ts vendored
View File

@@ -30,3 +30,13 @@ declare module '@theme/Layout' {
export default function Layout(props: Props): ReactNode;
}
declare module '@theme/Playground/Header' {
import type { ReactNode } from 'react';
export interface Props {
readonly children?: ReactNode;
}
export default function PlaygroundHeader(props: Props): ReactNode;
}

View File

@@ -0,0 +1,107 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { type ReactNode } from 'react';
import { LiveError, LivePreview } from 'react-live';
import BrowserOnly from '@docusaurus/BrowserOnly';
import { ErrorBoundaryErrorMessageFallback } from '@docusaurus/theme-common';
import ErrorBoundary from '@docusaurus/ErrorBoundary';
import Translate from '@docusaurus/Translate';
import PlaygroundHeader from '@theme/Playground/Header';
import styles from './styles.module.css';
// Get the theme wrapper for Superset components
function getThemeWrapper() {
if (typeof window === 'undefined') {
return ({ children }: { children: React.ReactNode }) => <>{children}</>;
}
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { themeObject } = require('@apache-superset/core/ui');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { App } = require('antd');
if (!themeObject?.SupersetThemeProvider) {
return ({ children }: { children: React.ReactNode }) => <>{children}</>;
}
return ({ children }: { children: React.ReactNode }) => (
<themeObject.SupersetThemeProvider>
<App>{children}</App>
</themeObject.SupersetThemeProvider>
);
} catch (e) {
console.error('[PlaygroundPreview] Failed to load theme provider:', e);
return ({ children }: { children: React.ReactNode }) => <>{children}</>;
}
}
function Loader() {
return <div>Loading...</div>;
}
function ThemedLivePreview(): ReactNode {
const ThemeWrapper = getThemeWrapper();
return (
<ThemeWrapper>
<LivePreview />
</ThemeWrapper>
);
}
function PlaygroundLivePreview(): ReactNode {
// No SSR for the live preview
// See https://github.com/facebook/docusaurus/issues/5747
return (
<BrowserOnly fallback={<Loader />}>
{() => (
<>
<ErrorBoundary
fallback={(params) => (
<ErrorBoundaryErrorMessageFallback {...params} />
)}
>
<ThemedLivePreview />
</ErrorBoundary>
<LiveError />
</>
)}
</BrowserOnly>
);
}
export default function PlaygroundPreview(): ReactNode {
return (
<>
<PlaygroundHeader>
<Translate
id="theme.Playground.result"
description="The result label of the live codeblocks"
>
Result
</Translate>
</PlaygroundHeader>
<div className={styles.playgroundPreview}>
<PlaygroundLivePreview />
</div>
</>
);
}

View File

@@ -16,12 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
@primary-color: #20a7c9;
@info-color: #66bcfe;
@success-color: #59c189;
@processing-color: #66bcfe;
@error-color: #e04355;
@highlight-color: #e04355;
@normal-color: #d9d9d9;
@white: #FFF;
@black: #000;
.playgroundPreview {
padding: 1rem;
background-color: var(--ifm-pre-background);
}

View File

@@ -18,36 +18,49 @@
*/
import React from 'react';
import { Button, Card, Input, Space, Tag, Tooltip } from 'antd';
// Import extension components from @apache-superset/core/ui
// This matches the established pattern used throughout the Superset codebase
// Resolved via webpack alias to superset-frontend/packages/superset-core/src/ui/components
import { Alert } from '@apache-superset/core/ui';
// Browser-only check for SSR safety
const isBrowser = typeof window !== 'undefined';
/**
* ReactLiveScope provides the scope for live code blocks.
* Any component added here will be available in ```tsx live blocks.
*
* To add more components:
* 1. Import the component from @apache-superset/core above
* 2. Add it to the scope object below
* Components are conditionally loaded only in the browser to avoid
* SSG issues with Emotion CSS-in-JS jsx runtime.
*
* Components are available by name, e.g.:
* <Button>Click me</Button>
* <Avatar size="large" />
* <Badge count={5} />
*/
const ReactLiveScope = {
// Base scope with React (always available)
const ReactLiveScope: Record<string, unknown> = {
// React core
React,
...React,
// Extension components from @apache-superset/core
Alert,
// Common Ant Design components (for demos)
Button,
Card,
Input,
Space,
Tag,
Tooltip,
};
// Only load Superset components in browser context
// This prevents SSG errors from Emotion CSS-in-JS
if (isBrowser) {
try {
// Dynamic require for browser-only execution
// eslint-disable-next-line @typescript-eslint/no-require-imports
const SupersetComponents = require('@superset/components');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { Alert } = require('@apache-superset/core/ui');
console.log('[ReactLiveScope] SupersetComponents keys:', Object.keys(SupersetComponents || {}).slice(0, 10));
console.log('[ReactLiveScope] Has Button?', 'Button' in (SupersetComponents || {}));
Object.assign(ReactLiveScope, SupersetComponents, { Alert });
console.log('[ReactLiveScope] Final scope keys:', Object.keys(ReactLiveScope).slice(0, 20));
} catch (e) {
console.error('[ReactLiveScope] Failed to load Superset components:', e);
}
}
export default ReactLiveScope;

View File

@@ -74,6 +74,14 @@ export default function Root({ children }) {
window._paq.push(['trackSiteSearch', keyword, category, resultsCount]);
};
// Helper to track page views
const trackPageView = (url, title) => {
if (devMode) {
console.log('Matomo trackPageView:', { url, title });
}
window._paq.push(['trackPageView']);
};
// Track external link clicks using domain as category (vendor-agnostic)
const handleLinkClick = (event) => {
@@ -221,7 +229,6 @@ export default function Root({ children }) {
trackDocsVersion();
if (devMode) {
console.log('Tracking page view:', currentPath, currentTitle);
window._paq.push(['setDomains', ['superset.apache.org']]);
window._paq.push([
'setCustomUrl',
@@ -233,7 +240,7 @@ export default function Root({ children }) {
window._paq.push(['setReferrerUrl', window.location.href]);
window._paq.push(['setDocumentTitle', currentTitle]);
window._paq.push(['trackPageView']);
trackPageView(currentPath, currentTitle);
// Check for 404 after page renders
setTimeout(track404, 500);

View File

@@ -18,6 +18,7 @@
*/
import path from 'path';
import webpack from 'webpack';
import type { Plugin } from '@docusaurus/types';
export default function webpackExtendPlugin(): Plugin<void> {
@@ -26,14 +27,86 @@ export default function webpackExtendPlugin(): Plugin<void> {
configureWebpack(config) {
const isDev = process.env.NODE_ENV === 'development';
// Use NormalModuleReplacementPlugin to forcefully replace react-table
// This is necessary because regular aliases don't work for modules in nested node_modules
const reactTableShim = path.resolve(__dirname, './shims/react-table.js');
config.plugins?.push(
new webpack.NormalModuleReplacementPlugin(
/^react-table$/,
reactTableShim,
),
);
// Stub out heavy third-party packages that are transitive dependencies of
// superset-frontend components. The barrel file (components/index.ts)
// re-exports all components, so webpack must resolve their imports even
// though these components are never rendered on the docs site.
const nullModuleShim = path.resolve(__dirname, './shims/null-module.js');
const heavyDepsPatterns = [
/^brace(\/|$)/, // ACE editor modes/themes
/^react-ace(\/|$)/,
/^ace-builds(\/|$)/,
/^react-js-cron(\/|$)/, // Cron picker + CSS
// react-resize-detector: NOT shimmed — DropdownContainer needs it at runtime
// for overflow detection. Resolves from superset-frontend/node_modules.
/^react-window(\/|$)/,
/^re-resizable(\/|$)/,
/^react-draggable(\/|$)/,
/^ag-grid-react(\/|$)/,
/^ag-grid-community(\/|$)/,
];
heavyDepsPatterns.forEach(pattern => {
config.plugins?.push(
new webpack.NormalModuleReplacementPlugin(pattern, nullModuleShim),
);
});
// Add YAML loader rule directly to existing rules
config.module?.rules?.push({
test: /\.ya?ml$/,
use: 'js-yaml-loader',
});
// 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',
);
config.module?.rules?.push({
test: /\.(tsx?|jsx?)$/,
include: supersetFrontendPath,
exclude: /node_modules/,
use: {
loader: 'swc-loader',
options: {
// 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',
},
},
},
},
},
});
return {
devtool: isDev ? 'eval-source-map' : config.devtool,
devtool: isDev ? false : config.devtool,
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
...(isDev && {
optimization: {
...config.optimization,
@@ -44,8 +117,16 @@ export default function webpackExtendPlugin(): Plugin<void> {
},
}),
resolve: {
// Add superset-frontend node_modules to module resolution
modules: [
...(config.resolve?.modules || []),
path.resolve(__dirname, '../../superset-frontend/node_modules'),
],
alias: {
...config.resolve.alias,
// Ensure single React instance across all modules (critical for hooks to work)
react: path.resolve(__dirname, '../node_modules/react'),
'react-dom': path.resolve(__dirname, '../node_modules/react-dom'),
// Allow importing from superset-frontend
src: path.resolve(__dirname, '../../superset-frontend/src'),
// '@superset-ui/core': path.resolve(
@@ -58,14 +139,29 @@ export default function webpackExtendPlugin(): Plugin<void> {
__dirname,
'../../superset-frontend/packages/superset-ui-core/src/components',
),
// Extension API package - allows docs to import from @apache-superset/core/ui
// This matches the established pattern used throughout the Superset codebase
// Point directly to components to avoid importing theme (which has font dependencies)
// Note: TypeScript types come from docs/src/types/apache-superset-core (see tsconfig.json)
// This split is intentional: webpack resolves actual source, tsconfig provides simplified types
// Also alias the full package path for internal imports within components
'@superset-ui/core/components': path.resolve(
__dirname,
'../../superset-frontend/packages/superset-ui-core/src/components',
),
// Use a shim for react-table to handle CommonJS to ES module interop
// react-table v7 is CommonJS, but Superset components import it with ES module syntax
'react-table': path.resolve(__dirname, './shims/react-table.js'),
// Extension API package - resolve @apache-superset/core and its sub-paths
// to source so the docs build doesn't depend on pre-built lib/ artifacts.
// More specific sub-path aliases must come first; webpack matches the
// longest prefix.
'@apache-superset/core/ui': path.resolve(
__dirname,
'../../superset-frontend/packages/superset-core/src/ui/components',
'../../superset-frontend/packages/superset-core/src/ui',
),
'@apache-superset/core/api/core': path.resolve(
__dirname,
'../../superset-frontend/packages/superset-core/src/api/core',
),
'@apache-superset/core': path.resolve(
__dirname,
'../../superset-frontend/packages/superset-core/src',
),
// Add proper Storybook aliases
'@storybook/blocks': path.resolve(
@@ -123,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
};
},
};

View File

@@ -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/atomic-design.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

View File

@@ -0,0 +1,21 @@
<!--
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.
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 568 501" width="40" height="40" fill="#1185FE">
<path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.889-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C9.945 203.659 0 75.291 0 57.946 0-28.906 76.135-1.612 123.121 33.664z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,23 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="40" height="40" fill="none" stroke="#484848" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="2" y1="12" x2="22" y2="12"/>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,21 @@
<!--
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.
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="40" height="40" fill="#0A66C2">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

21
docs/static/img/community/x-symbol.svg vendored Normal file
View File

@@ -0,0 +1,21 @@
<!--
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.
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="40" height="40" fill="#484848">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
docs/static/img/databases/alloydb.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

1
docs/static/img/databases/cratedb.svg vendored Normal file
View File

@@ -0,0 +1 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 436 58" width="436" height="58"><style>.a{fill:#009dc7}</style><title>CrateDB</title><path class="a" d="m3.6 0h14.5v14.5h-14.5z"/><path class="a" d="m25.4 0h14.5v14.5h-14.5z"/><path class="a" d="m47.2 0h14.5v36.2h-14.5z"/><path class="a" d="m3.6 21.7h36.3v14.5h-36.3z"/><path class="a" d="m3.6 43.4h58.1v14.5h-58.1z"/><path d="m249.5 16.9h12.1v8.4h-12.1v19.3h12.1v8.4h-10.4c-1.5 0.1-2.9-0.2-4.3-0.7-1.3-0.6-2.6-1.3-3.6-2.3-1.1-1-1.9-2.2-2.5-3.6-0.6-1.3-1-2.7-1-4.2v-16.9h-9.7v-8.4h9.7v-12.1h9.7z"/><path d="m159.8 22.9q0-1.2 0.5-2.3 0.4-1.1 1.3-2 0.9-0.8 2-1.3 1.1-0.4 2.3-0.4h12.1v8.4h-13.3q-1 0-1.9 0.4-0.8 0.4-1.5 1-0.7 0.7-1.1 1.6-0.3 0.9-0.3 1.9v22.9h-9.7v-36.2h7.2l1.2 6h1.2z"/><path d="m90.8 16.9c0-1.6 0.3-3.2 0.9-4.6 0.6-1.5 1.5-2.8 2.7-3.9 1.1-1.2 2.4-2 3.9-2.7 1.5-0.6 3-0.9 4.6-0.9h36.4v9.7h-38.8v28.9h38.8v9.7h-36.4c-1.6 0-3.1-0.3-4.6-0.9-1.5-0.6-2.8-1.5-3.9-2.6-1.2-1.2-2.1-2.5-2.7-4-0.6-1.4-0.9-3-0.9-4.6v-24.1z"/><path fill-rule="evenodd" d="m358.9 6.1c2.1 0.9 4 2.1 5.5 3.7 1.6 1.5 2.9 3.4 3.7 5.5 0.9 2 1.3 4.2 1.3 6.4v14.5c0 2.2-0.4 4.4-1.3 6.5-0.8 2-2.1 3.9-3.7 5.4-1.5 1.6-3.4 2.8-5.5 3.7-2 0.8-4.2 1.3-6.4 1.3h-34v-48.3h34c2.2 0 4.4 0.5 6.4 1.3zm0.3 12.8q-0.6-1.3-1.6-2.3-1-1-2.4-1.6-1.3-0.5-2.7-0.5h-24.3v28.9h24.3q1.4 0 2.7-0.5 1.4-0.6 2.4-1.6 1-1 1.6-2.3 0.5-1.4 0.5-2.8v-14.5q0-1.4-0.5-2.8z"/><path fill-rule="evenodd" d="m212.7 16.9c1.3 0 2.5 0.2 3.7 0.7 1.2 0.5 2.3 1.2 3.2 2.1 0.9 0.9 1.7 1.9 2.2 3.1 0.4 1.2 0.7 2.5 0.7 3.7v26.6h-7.3l-1.2-4.8h-1.2q0 0.9-0.4 1.8-0.3 0.9-1 1.6-0.7 0.7-1.6 1-0.9 0.4-1.8 0.4h-14c-1.5 0.1-2.9-0.2-4.3-0.7-1.4-0.5-2.6-1.3-3.7-2.3-1.1-1-1.9-2.2-2.5-3.5-0.6-1.3-1-2.8-1-4.2 0-1.4 0.3-2.8 0.9-4.1 0.6-1.2 1.4-2.4 2.4-3.4 1-0.9 2.2-1.7 3.5-2.2 1.3-0.5 2.6-0.7 4-0.7h19.4v-7.3h-25.4v-7.8zm-3.6 28.9q0.7 0 1.3-0.2 0.7-0.3 1.2-0.8 0.5-0.5 0.8-1.2 0.3-0.7 0.3-1.4v-3.6h-17.6c-0.9 0-1.8 0.4-2.5 1.1-0.7 0.6-1.1 1.6-1.1 2.5 0 1 0.4 1.9 1.1 2.6 0.6 0.7 1.6 1 2.5 1.1h14z"/><path fill-rule="evenodd" d="m296.7 16.9c1.6 0 3.2 0.3 4.7 0.9 1.4 0.6 2.8 1.5 3.9 2.6 1.1 1.1 2 2.5 2.6 3.9 0.6 1.5 0.9 3.1 0.9 4.7v9.6h-30.2v6.6h27.8v7.9h-25.4c-1.6 0-3.2-0.3-4.6-0.9-1.5-0.6-2.8-1.5-4-2.6-1.1-1.2-2-2.5-2.6-4-0.6-1.4-0.9-3-0.9-4.6v-12c0-1.6 0.3-3.2 0.9-4.7 0.6-1.4 1.5-2.8 2.6-3.9 1.2-1.1 2.5-2 4-2.6 1.4-0.6 3-0.9 4.6-0.9h15.7zm3.7 15.1v-3.7q0-0.7-0.3-1.4-0.3-0.6-0.8-1.1-0.5-0.5-1.2-0.8-0.6-0.3-1.3-0.3h-14.6q-0.7 0-1.4 0.3-0.6 0.3-1.1 0.8-0.6 0.5-0.8 1.2-0.3 0.6-0.3 1.4v3.6z"/><path fill-rule="evenodd" d="m427.3 27.7q0 0 0 0 0 0 0 0.1zm0 0c2.3 1.9 3.9 4.4 4.6 7.3 0.8 2.9 0.6 5.9-0.4 8.7-1.1 2.7-2.9 5.1-5.3 6.8-2.5 1.7-5.4 2.6-8.3 2.6h-38.8v-48.2h36.3c2.7 0 5.3 0.7 7.6 2.1 2.3 1.3 4.1 3.3 5.3 5.7 1.3 2.3 1.8 5 1.6 7.6-0.2 2.6-1.1 5.2-2.6 7.4zm-38.5-3.7h26.6c1.3 0 2.5-0.5 3.4-1.4 0.9-0.9 1.4-2.1 1.4-3.4 0-1.2-0.5-2.5-1.4-3.4-0.9-0.9-2.1-1.4-3.4-1.4h-26.6zm32.5 17.5c0.9-1 1.3-2.3 1.3-3.6 0-1.4-0.4-2.6-1.3-3.6-0.9-1-2.1-1.7-3.4-1.9h-29.1v10.9h29.1c1.3-0.2 2.5-0.8 3.4-1.8z"/></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
docs/static/img/databases/mongodb.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/static/img/databases/neon.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

182
docs/static/img/databases/risingwave.svg vendored Normal file
View File

@@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="1100"
height="300"
viewBox="0 0 291.04166 79.375002"
version="1.1"
id="svg93"
inkscape:version="1.1.2 (b8e25be8, 2022-02-05)"
sodipodi:docname="rwreadme.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview95"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
units="px"
inkscape:zoom="0.39692378"
inkscape:cx="578.19665"
inkscape:cy="137.30596"
inkscape:window-width="1584"
inkscape:window-height="1027"
inkscape:window-x="336"
inkscape:window-y="25"
inkscape:window-maximized="0"
inkscape:current-layer="g19" />
<defs
id="defs90">
<style
id="style97">.cls-1{fill:#3a7ceb;}.cls-2{fill:#e23e2b;}.cls-3{fill:#f0b400;}.cls-4{fill:#4acfd2;}</style>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath23">
<path
d="M 0,1080 H 1920 V 0 H 0 Z"
id="path21" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath91">
<path
d="M 0,1080 H 1920 V 0 H 0 Z"
id="path89" />
</clipPath>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g19"
clip-path="url(#clipPath23)"
transform="matrix(0.35277777,0,0,-0.35277777,-280.32591,259.26734)">
<g
id="g355"
transform="matrix(1.7,0,0,1.7,-756.33217,-454.30518)">
<g
id="g25"
transform="translate(1074.2552,637.33841)">
<path
d="m 0,0 h 1.913 c 1.998,0 3.532,0.414 4.601,1.241 1.068,0.827 1.602,2.016 1.602,3.567 0,1.55 -0.534,2.739 -1.602,3.567 -1.069,0.827 -2.603,1.24 -4.601,1.24 H 0 Z M 22.28,-21.557 H 9.667 L 0,-6.565 v -8.311 c 0,-3.689 -2.991,-6.681 -6.681,-6.681 h -3.451 V 17.421 H 5.635 c 2.171,0 4.066,-0.319 5.686,-0.956 1.62,-0.638 2.955,-1.508 4.007,-2.611 1.05,-1.103 1.843,-2.378 2.378,-3.825 0.533,-1.448 0.801,-2.999 0.801,-4.653 0,-2.964 -0.716,-5.368 -2.146,-7.211 -1.43,-1.844 -3.541,-3.094 -6.332,-3.748 z"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path27" />
</g>
<g
id="g29"
transform="translate(1108.2186,638.84481)">
<path
d="m 0,0 v -17.487 c 0,-3.079 -2.497,-5.576 -5.576,-5.576 h -3.78 V 3.456 h 5.9 C -1.547,3.456 0,1.909 0,0 m -9.977,13.64 c 0,0.724 0.138,1.404 0.414,2.042 0.275,0.637 0.654,1.197 1.137,1.68 0.482,0.482 1.042,0.861 1.68,1.138 0.638,0.275 1.318,0.413 2.042,0.413 0.724,0 1.404,-0.138 2.042,-0.413 0.637,-0.277 1.197,-0.656 1.68,-1.138 0.482,-0.483 0.861,-1.043 1.137,-1.68 0.276,-0.638 0.414,-1.318 0.414,-2.042 0,-0.723 -0.138,-1.405 -0.414,-2.042 C -0.121,10.96 -0.5,10.4 -0.982,9.918 -1.465,9.435 -2.025,9.056 -2.662,8.781 -3.3,8.504 -3.98,8.367 -4.704,8.367 c -0.724,0 -1.404,0.137 -2.042,0.414 -0.638,0.275 -1.198,0.654 -1.68,1.137 -0.483,0.482 -0.862,1.042 -1.137,1.68 -0.276,0.637 -0.414,1.319 -0.414,2.042"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path31" />
</g>
<g
id="g33"
transform="translate(1131.1708,635.47761)">
<path
d="m 0,0 c -1.724,0.896 -3.412,1.344 -5.066,1.344 -0.862,0 -1.56,-0.173 -2.094,-0.517 -0.534,-0.345 -0.801,-0.81 -0.801,-1.396 0,-0.31 0.043,-0.56 0.129,-0.749 0.086,-0.19 0.276,-0.371 0.569,-0.543 0.292,-0.173 0.723,-0.328 1.292,-0.466 0.569,-0.137 1.319,-0.31 2.249,-0.516 2.585,-0.517 4.514,-1.44 5.79,-2.766 1.274,-1.327 1.912,-3.024 1.912,-5.092 0,-1.551 -0.31,-2.946 -0.93,-4.187 -0.62,-1.241 -1.482,-2.284 -2.585,-3.128 -1.103,-0.845 -2.429,-1.499 -3.98,-1.964 -1.551,-0.465 -3.257,-0.698 -5.118,-0.698 -3.585,0 -7.117,1.016 -10.597,3.05 l 3.412,6.617 c 2.618,-1.793 5.099,-2.688 7.443,-2.688 0.861,0 1.568,0.189 2.12,0.569 0.551,0.378 0.827,0.86 0.827,1.447 0,0.344 -0.044,0.628 -0.129,0.853 -0.087,0.224 -0.268,0.43 -0.543,0.62 -0.276,0.189 -0.681,0.362 -1.215,0.517 -0.535,0.155 -1.232,0.318 -2.094,0.491 -2.894,0.586 -4.919,1.456 -6.073,2.611 -1.156,1.154 -1.732,2.748 -1.732,4.781 0,1.482 0.275,2.818 0.827,4.007 0.551,1.189 1.344,2.197 2.378,3.024 1.033,0.827 2.282,1.465 3.748,1.913 1.464,0.447 3.093,0.672 4.885,0.672 2.929,0 5.772,-0.569 8.529,-1.706 z"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path35" />
</g>
<g
id="g37"
transform="translate(1148.2289,638.84481)">
<path
d="m 0,0 v -17.487 c 0,-3.079 -2.497,-5.576 -5.576,-5.576 h -3.78 V 3.456 h 5.9 C -1.547,3.456 0,1.909 0,0 m -9.977,13.64 c 0,0.724 0.138,1.404 0.414,2.042 0.275,0.637 0.654,1.197 1.137,1.68 0.482,0.482 1.042,0.861 1.68,1.138 0.638,0.275 1.318,0.413 2.042,0.413 0.724,0 1.404,-0.138 2.042,-0.413 0.637,-0.277 1.197,-0.656 1.68,-1.138 0.482,-0.483 0.861,-1.043 1.137,-1.68 0.276,-0.638 0.414,-1.318 0.414,-2.042 0,-0.723 -0.138,-1.405 -0.414,-2.042 C -0.121,10.96 -0.5,10.4 -0.982,9.918 -1.465,9.435 -2.025,9.056 -2.662,8.781 -3.3,8.504 -3.98,8.367 -4.704,8.367 c -0.724,0 -1.404,0.137 -2.042,0.414 -0.638,0.275 -1.198,0.654 -1.68,1.137 -0.483,0.482 -0.862,1.042 -1.137,1.68 -0.276,0.637 -0.414,1.319 -0.414,2.042"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path39" />
</g>
<g
id="g41"
transform="translate(1153.6049,642.30081)">
<path
d="m 0,0 h 9.356 v -3.36 c 1.275,1.551 2.568,2.593 3.878,3.128 1.309,0.534 2.843,0.801 4.6,0.801 1.862,0 3.455,-0.302 4.782,-0.905 1.326,-0.603 2.456,-1.456 3.386,-2.559 0.758,-0.896 1.275,-1.896 1.551,-2.998 0.276,-1.103 0.414,-2.361 0.414,-3.774 v -11.276 c 0,-3.079 -2.497,-5.576 -5.576,-5.576 H 18.61 v 13.389 c 0,1.309 -0.095,2.369 -0.284,3.179 -0.19,0.809 -0.526,1.455 -1.008,1.939 -0.414,0.413 -0.879,0.705 -1.396,0.878 -0.517,0.173 -1.069,0.259 -1.654,0.259 -1.586,0 -2.801,-0.474 -3.645,-1.422 -0.845,-0.948 -1.267,-2.3 -1.267,-4.058 v -8.588 c 0,-3.079 -2.496,-5.576 -5.575,-5.576 H 0 Z"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path43" />
</g>
<g
id="g45"
transform="translate(1195.58,629.17041)">
<path
d="m 0,0 c 0,-0.862 0.155,-1.663 0.465,-2.404 0.31,-0.741 0.732,-1.388 1.267,-1.938 0.534,-0.552 1.171,-0.982 1.913,-1.292 0.74,-0.311 1.542,-0.466 2.403,-0.466 0.827,0 1.611,0.155 2.352,0.466 0.741,0.31 1.378,0.74 1.913,1.292 0.534,0.55 0.964,1.197 1.292,1.938 0.328,0.741 0.492,1.526 0.492,2.353 0,0.861 -0.164,1.654 -0.492,2.378 C 11.277,3.05 10.838,3.688 10.287,4.239 9.736,4.79 9.089,5.221 8.349,5.532 7.607,5.842 6.841,5.997 6.048,5.997 5.221,5.997 4.446,5.833 3.722,5.506 2.999,5.178 2.36,4.747 1.81,4.213 1.258,3.679 0.818,3.05 0.491,2.327 0.163,1.603 0,0.827 0,0 m 16.926,13.13 c 2.243,0 4.062,-1.818 4.062,-4.062 V -11.58 c 0,-1.482 -0.086,-2.808 -0.259,-3.98 -0.172,-1.171 -0.413,-2.205 -0.723,-3.102 -0.414,-1.137 -1.06,-2.196 -1.939,-3.179 -0.879,-0.982 -1.938,-1.835 -3.179,-2.558 -1.241,-0.725 -2.645,-1.293 -4.213,-1.706 -1.569,-0.414 -3.266,-0.621 -5.092,-0.621 -2.068,0 -3.963,0.259 -5.687,0.776 -1.723,0.516 -3.23,1.232 -4.522,2.145 -1.293,0.913 -2.353,1.982 -3.18,3.205 -0.827,1.224 -1.379,2.559 -1.654,4.006 H 1.085 c 0.655,-1.758 2.137,-2.636 4.446,-2.636 1.965,0 3.472,0.551 4.524,1.654 1.05,1.103 1.576,2.637 1.576,4.601 v 2.533 c -0.69,-0.655 -1.353,-1.198 -1.99,-1.628 -0.638,-0.432 -1.284,-0.785 -1.938,-1.06 -0.656,-0.276 -1.345,-0.474 -2.068,-0.595 -0.724,-0.12 -1.517,-0.181 -2.378,-0.181 -1.896,0 -3.636,0.328 -5.221,0.983 -1.586,0.654 -2.956,1.568 -4.11,2.739 -1.155,1.172 -2.051,2.576 -2.688,4.213 -0.638,1.637 -0.956,3.455 -0.956,5.454 0,2.033 0.343,3.92 1.033,5.661 0.689,1.74 1.629,3.256 2.818,4.549 1.189,1.292 2.601,2.308 4.239,3.05 1.636,0.74 3.42,1.111 5.35,1.111 3.136,0 5.772,-1.172 7.909,-3.515 v 2.791 z"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path47" />
</g>
<g
id="g49"
transform="translate(1230.2025,651.31941)">
<path
d="M 0,0 5.803,-21.735 14.074,3.44 h 8.064 L 30.409,-21.735 36.211,0 c 0.541,2.029 2.379,3.44 4.479,3.44 h 7.037 L 36.096,-35.538 H 25.757 l -7.651,22.591 -7.651,-22.591 H 0.116 L -11.515,3.44 h 7.036 C -2.379,3.44 -0.542,2.029 0,0"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path51" />
</g>
<g
id="g53"
transform="translate(1287.0272,629.11921)">
<path
d="m 0,0 c 0,-0.862 0.155,-1.664 0.465,-2.404 0.31,-0.742 0.732,-1.388 1.267,-1.939 0.534,-0.551 1.171,-0.982 1.913,-1.292 0.74,-0.31 1.542,-0.465 2.403,-0.465 0.827,0 1.611,0.155 2.352,0.465 0.741,0.31 1.378,0.741 1.913,1.292 0.534,0.551 0.964,1.189 1.292,1.913 0.328,0.724 0.492,1.499 0.492,2.326 0,0.828 -0.164,1.611 -0.492,2.353 -0.328,0.74 -0.758,1.386 -1.292,1.938 C 9.778,4.738 9.141,5.169 8.4,5.479 7.659,5.79 6.875,5.945 6.048,5.945 5.187,5.945 4.385,5.79 3.645,5.479 2.903,5.169 2.266,4.738 1.732,4.187 1.197,3.635 0.775,3.006 0.465,2.3 0.155,1.593 0,0.827 0,0 m 11.787,13.182 h 6.423 c 1.648,0 2.985,-1.336 2.985,-2.984 v -23.535 h -9.408 v 2.946 c -2,-2.516 -4.705,-3.774 -8.117,-3.774 -1.93,0 -3.705,0.354 -5.324,1.06 -1.62,0.706 -3.033,1.697 -4.239,2.973 -1.207,1.274 -2.146,2.774 -2.817,4.497 -0.672,1.723 -1.008,3.601 -1.008,5.635 0,1.895 0.327,3.696 0.982,5.402 0.654,1.706 1.568,3.196 2.739,4.472 1.172,1.274 2.567,2.282 4.188,3.023 1.619,0.741 3.412,1.112 5.376,1.112 3.308,0 6.048,-1.155 8.22,-3.464 z"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path55" />
</g>
<g
id="g57"
transform="translate(1322.8539,639.17391)">
<path
d="M 0,0 4.856,-10.468 9.806,0.03 c 0.892,1.891 2.794,3.097 4.884,3.097 h 7.018 L 7.906,-23.392 H 1.651 L -11.996,3.127 h 7.099 C -2.795,3.127 -0.884,1.907 0,0"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path59" />
</g>
<g
id="g61"
transform="translate(1364.8783,632.89261)">
<path
d="M 0,0 C -0.31,1.31 -0.948,2.36 -1.913,3.153 -2.878,3.946 -4.05,4.342 -5.428,4.342 -6.875,4.342 -8.056,3.963 -8.969,3.205 -9.882,2.447 -10.46,1.378 -10.701,0 Z m -10.959,-5.428 c 0,-4.032 1.895,-6.048 5.687,-6.048 2.032,0 3.566,0.827 4.6,2.481 h 9.047 c -1.828,-6.066 -6.394,-9.098 -13.699,-9.098 -2.241,0 -4.291,0.336 -6.152,1.008 -1.861,0.672 -3.456,1.628 -4.782,2.869 -1.327,1.241 -2.352,2.722 -3.075,4.446 -0.724,1.723 -1.086,3.652 -1.086,5.79 0,2.205 0.344,4.195 1.034,5.97 0.689,1.775 1.671,3.283 2.947,4.524 1.274,1.24 2.817,2.196 4.626,2.869 1.809,0.672 3.851,1.008 6.126,1.008 2.24,0 4.256,-0.336 6.048,-1.008 C 2.153,8.71 3.67,7.736 4.911,6.462 6.152,5.187 7.099,3.627 7.754,1.784 8.409,-0.061 8.736,-2.137 8.736,-4.446 v -0.982 z"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path63" />
</g>
</g>
<g
id="g65"
transform="translate(834.46932,615.63096)"
style="fill:#3a7ceb;fill-opacity:1">
<path
d="m 0,0 c 2.631,-2.663 5.385,-5.128 8.245,-7.398 7.412,-5.887 15.531,-10.457 24.042,-13.707 7.92,-3.025 16.177,-4.907 24.518,-5.645 25.774,-2.28 52.344,6.361 72.192,25.97 18.539,18.316 27.468,42.562 26.977,66.71 -1.092,5.997 -3.509,5.222 -3.427,8.318 0.575,-0.466 3.148,-2.951 5.036,-5.649 23.639,-35.608 19.594,-84.095 -11.954,-115.265 -28.918,-28.57 -72.105,-33.857 -106.307,-16.006 -8.184,4.272 -15.854,9.866 -22.69,16.786 -3.119,3.157 -5.96,6.485 -8.524,9.952 C 1.179,-26.566 -3.725,-16.178 -6.608,-5.365 -8.059,0.073 -8.999,5.618 -9.426,11.195 -6.631,7.278 -3.49,3.532 0,0"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path67" />
</g>
<g
id="g69"
transform="translate(893.24962,688.94836)">
<path
d="m 0,0 c 1.724,-1.745 2.919,-3.811 3.592,-6.004 0.215,-1.013 0.411,-2.032 0.592,-3.05 3.02,-17.13 1.122,-34.95 -5.679,-51.195 -2.925,13.93 -9.133,27.311 -18.638,38.906 -0.867,1.063 -1.768,2.11 -2.691,3.141 -4.817,5.221 -2.949,13.376 2.064,18.329 C -14.993,5.825 -5.698,5.767 0,0"
style="fill:#e23e2b;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path71" />
</g>
<g
id="g73"
transform="translate(903.34482,697.22136)">
<path
d="m 0,0 c 7.721,7.628 20.162,7.553 27.791,-0.168 4.886,-4.947 7.056,-11 5.637,-17.315 l -0.199,-0.886 C 29.492,-34.683 21.223,-50.161 8.436,-62.794 c -5.719,-5.651 -11.999,-10.39 -18.655,-14.22 -2.539,-1.463 -5.133,-2.791 -7.773,-3.99 2.476,4.025 4.608,8.199 6.402,12.483 6.8,16.244 8.698,34.064 5.678,51.194 -0.022,0.255 -0.235,1.283 -0.235,1.283 C -6.915,-10.163 -4.545,-4.491 0,0"
style="fill:#f0b400;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path75" />
</g>
<g
id="g77"
transform="translate(885.35312,616.21746)">
<path
d="m 0,0 c 2.64,1.199 5.234,2.527 7.773,3.99 6.656,3.83 12.935,8.569 18.655,14.22 12.786,12.633 21.056,28.111 24.792,44.425 0.2,0.873 0.296,1.518 0.473,2.409 1.024,5.123 3.642,9.917 7.64,13.867 10.654,10.527 27.456,11.077 38.355,-0.233 2.733,-2.837 7.101,-6.227 7.53,-15.069 0.007,-3.738 -0.219,-7.479 -0.674,-11.198 C 102.142,32.716 93.324,13.662 78.114,-1.365 58.267,-20.975 31.695,-29.616 5.922,-27.337 c -8.342,0.739 -16.599,2.622 -24.518,5.645 -8.512,3.249 -16.631,7.821 -24.044,13.707 C -28.138,-8.771 -13.481,-6.109 0,0"
style="fill:#4acfd2;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path79" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 KiB

220
docs/static/img/logos/club25deagosto.svg vendored Normal file
View File

@@ -0,0 +1,220 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="419.92645mm"
height="664.284mm"
viewBox="0 0 419.92645 664.28402"
version="1.1"
id="svg1"
xml:space="preserve"
sodipodi:docname="Logo 25 full transparente.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.28180672"
inkscape:cx="-35.485314"
inkscape:cy="942.13509"
inkscape:window-width="1920"
inkscape:window-height="1008"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2"
showgrid="false"
showguides="true"><inkscape:grid
id="grid792"
units="mm"
originx="127.13166"
originy="294.77868"
spacingx="1"
spacingy="1"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="5"
enabled="true"
visible="false" /></sodipodi:namedview><defs
id="defs1" /><g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Capa 2"
transform="matrix(16.785907,0,0,16.785907,-1379.0426,-1310.3372)"
style="display:inline"><path
style="display:none;fill:#ffffff;stroke:#ffffff;stroke-width:33.2612"
d="M 969.02922,898.66558 968.74908,63.873566 h 49.67942 49.6795 v 12.749576 c 0,20.163418 13.2134,79.715458 25.2704,113.891858 41.2493,116.92445 126.0825,213.33066 235.6456,267.79281 33.7227,16.76301 37.3546,19.48775 35.5517,26.67136 -2.4596,9.79977 -383.39445,1214.40233 -390.32118,1234.28433 -4.25551,12.2146 -4.98422,-102.2238 -5.2253,-820.59792 z"
id="path4"
transform="matrix(0.01576223,0,0,0.01576223,82.154786,78.061745)"
inkscape:label="Relleno blanco derecha" /><path
style="display:none;fill:#ffffff;stroke:#ffffff;stroke-width:33.2612"
d="M 418.43781,1108.0289 221.50466,484.37454 255.77157,467.94466 C 342.51452,426.35428 414.22899,360.20279 462.70973,277.05901 493.97419,223.4409 518.51143,148.29568 523.79549,89.984045 l 2.36607,-26.110479 h 45.64145 45.64146 V 897.77845 c 0,458.64765 -0.46653,833.90485 -1.03674,833.90485 -0.57022,0 -89.65669,-280.6444 -197.96992,-623.6544 z"
id="path5"
transform="matrix(0.01576223,0,0,0.01576223,82.154786,78.061745)"
inkscape:label="Relleno blanco izquieda" /><path
style="display:inline;fill:#1d0695;fill-opacity:1;stroke:#1d0695;stroke-width:1.00005;stroke-dasharray:none;stroke-opacity:1"
id="path788-6"
d="m -99.481003,78.564575 a 7,7 0 0 1 -4.836877,6.657396 l -2.16312,-6.657396 z"
sodipodi:type="arc"
sodipodi:arc-type="slice"
sodipodi:start="0"
sodipodi:end="1.2566371"
sodipodi:ry="7"
sodipodi:rx="7"
sodipodi:cy="78.564575"
sodipodi:cx="-106.481"
inkscape:label="Esquina izquierda"
transform="scale(-1,1)" /><path
style="fill:#1d0695;fill-opacity:1;stroke:#1d0695;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
id="path788"
d="m 89.933891,78.65564 a 7,7 0 0 1 -4.856213,6.663646 L 82.933891,78.65564 Z"
sodipodi:type="arc"
sodipodi:arc-type="slice"
sodipodi:start="0"
sodipodi:end="1.2595395"
sodipodi:ry="7"
sodipodi:rx="7"
sodipodi:cy="78.65564"
sodipodi:cx="82.933891"
inkscape:label="Esquina izquierda" /><path
style="display:inline;fill:#ff0000;stroke:#ff0000;stroke-width:0.882544;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 92.343042,79.504907 4.598918,0.0049 0.0017,26.274323 -4.588022,-9.9e-4 z"
id="path792"
inkscape:label="Rectángulo"
sodipodi:nodetypes="ccccc" /><path
style="display:inline;opacity:1;fill:#000000;fill-opacity:0;stroke:#1d0695;stroke-width:1.00005;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 82.837015,78.563312 23.651695,-0.0015 -11.838335,37.414748 z"
id="path786"
sodipodi:nodetypes="cccc"
inkscape:label="Triángulo"
transform="translate(-1.2708333e-6)" /><path
style="fill:#1d0695;fill-opacity:1;stroke:#1d0695;stroke-width:1.03941;stroke-dasharray:none;stroke-opacity:1"
d="m 105.21073,301.76961 c -6.53995,-20.71706 -11.875109,-37.6831 -11.855911,-37.70229 0.0192,-0.0192 10.763101,-0.0223 23.875341,-0.007 l 23.84043,0.028 -11.92048,37.67543 c -6.55626,20.72148 -11.94929,37.67492 -11.98452,37.67432 -0.0352,-6.1e-4 -5.41492,-16.95144 -11.95486,-37.6685 z"
id="path54"
transform="matrix(0.10669253,0,0,0.10669253,82.149909,78.059739)"
inkscape:label="Relleno azul abajo" /></g><g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Texto"
transform="matrix(6.768873,0,0,6.768873,-47.398718,-0.20588049)"><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.2451px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.5938"
x="17.628099"
y="15.110926"
id="text16"
transform="scale(1.8040993,0.55429321)"><tspan
sodipodi:role="line"
id="tspan16"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.2451px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.5938"
x="17.628099"
y="15.110926">C</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:10.8582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.53895"
x="19.495819"
y="25.445749"
id="text17"
transform="scale(1.6638658,0.60101)"><tspan
sodipodi:role="line"
id="tspan17"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:10.8582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.53895"
x="19.495819"
y="25.445749">A</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:7.95775px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.12787"
x="27.980883"
y="25.586826"
id="text18"
transform="scale(1.1569098,0.86437164)"><tspan
sodipodi:role="line"
id="tspan18"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:7.95775px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.12787"
x="27.980883"
y="25.586826">25</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:7.99489px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.13313"
x="27.62228"
y="33.788048"
id="text19"
transform="scale(1.1646165,0.85865179)"><tspan
sodipodi:role="line"
id="tspan19"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:7.99489px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.13313"
x="27.62228"
y="33.788048">DE</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:10.8582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.53895"
x="19.49581"
y="59.22831"
id="text20"
transform="scale(1.6638665,0.60100975)"><tspan
sodipodi:role="line"
id="tspan20"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:10.8582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.53895"
x="19.49581"
y="59.22831">A</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.2553px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.59524"
x="17.650023"
y="76.522369"
id="text21"
transform="scale(1.8063001,0.55361786)"><tspan
sodipodi:role="line"
id="tspan21"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.2553px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.59524"
x="17.650023"
y="76.522369">G</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.0582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.5673"
x="18.147587"
y="86.019577"
id="text22"
transform="scale(1.7637218,0.56698285)"><tspan
sodipodi:role="line"
id="tspan22"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.0582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.5673"
x="18.147587"
y="86.019577">O</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.0296px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.56325"
x="18.269663"
y="97.356186"
id="text23"
transform="scale(1.7575891,0.56896121)"><tspan
sodipodi:role="line"
id="tspan23"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.0296px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.56325"
x="18.269663"
y="97.356186">S</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:10.6716px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.51252"
x="20.083975"
y="100.755"
id="text24"
transform="scale(1.6258383,0.61506731)"><tspan
sodipodi:role="line"
id="tspan24"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:10.6716px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.51252"
x="20.083975"
y="100.755">T</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.0582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.5673"
x="18.147587"
y="119.65324"
id="text25"
transform="scale(1.7637218,0.56698285)"><tspan
sodipodi:role="line"
id="tspan25"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.0582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.5673"
x="18.147587"
y="119.65324">O</tspan></text></g></svg>

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/static/img/logos/hpe.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
docs/static/img/logos/xnet.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
docs/static/img/netlify.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 636 KiB

After

Width:  |  Height:  |  Size: 104 KiB

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