mirror of
https://github.com/apache/superset.git
synced 2026-04-28 12:34:23 +00:00
Compare commits
540 Commits
docs/testi
...
supernauts
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfa6c8a2e3 | ||
|
|
f86511a956 | ||
|
|
b91ff3ab0d | ||
|
|
8b1c41a012 | ||
|
|
71a38305d9 | ||
|
|
4ae62dcae8 | ||
|
|
da31e82b6a | ||
|
|
18d3da81ca | ||
|
|
2b1c72a92c | ||
|
|
75c6da97b2 | ||
|
|
6439440260 | ||
|
|
989bb3432f | ||
|
|
b441844ca6 | ||
|
|
09a1788a8b | ||
|
|
cde9abfce2 | ||
|
|
0bcefe34ac | ||
|
|
649112aa1f | ||
|
|
120ca5cf8f | ||
|
|
0035da83af | ||
|
|
019f9442ae | ||
|
|
084f9832c7 | ||
|
|
8a339febeb | ||
|
|
e5579ed939 | ||
|
|
d5dbd06824 | ||
|
|
a1b5b92265 | ||
|
|
92c63a54e4 | ||
|
|
e5b7e38a30 | ||
|
|
edcb38517f | ||
|
|
51a6b30179 | ||
|
|
a588668899 | ||
|
|
9cf86c1533 | ||
|
|
740b328199 | ||
|
|
375bcd00ba | ||
|
|
076d4950d0 | ||
|
|
0e9cffe12e | ||
|
|
46e21c3003 | ||
|
|
1f5df7407f | ||
|
|
e1c022344e | ||
|
|
78081755aa | ||
|
|
955953b467 | ||
|
|
ead19f9ba3 | ||
|
|
c16ca9527c | ||
|
|
d674d54e2e | ||
|
|
0e8b69089d | ||
|
|
0e8c420002 | ||
|
|
e433cd5f69 | ||
|
|
c1b52cb8ed | ||
|
|
dba3fdfadf | ||
|
|
b7a541a9da | ||
|
|
1bde06b366 | ||
|
|
e8927ca3b3 | ||
|
|
9d58599329 | ||
|
|
858a72d8c1 | ||
|
|
c79c85cdfe | ||
|
|
47ea316792 | ||
|
|
5b38a1a0d4 | ||
|
|
f2e677c150 | ||
|
|
d992a5836f | ||
|
|
ee2ab7e078 | ||
|
|
683a65488f | ||
|
|
751804d044 | ||
|
|
0d3c4d5d22 | ||
|
|
b8b7b958d9 | ||
|
|
0092cdca81 | ||
|
|
2120569267 | ||
|
|
4b7ae3a8f7 | ||
|
|
a64e5e15fc | ||
|
|
4f14eddf73 | ||
|
|
70a3e8fb42 | ||
|
|
954da8a3cc | ||
|
|
4c9a463db3 | ||
|
|
47dbdd7a59 | ||
|
|
abc0678454 | ||
|
|
dc403145ed | ||
|
|
955b3b2b19 | ||
|
|
ab80ec8066 | ||
|
|
bb22eb1ca8 | ||
|
|
8d7c83419c | ||
|
|
d300d69f8f | ||
|
|
9e7e813255 | ||
|
|
f4b919bf7d | ||
|
|
3940354120 | ||
|
|
b35b1d7633 | ||
|
|
0131e542e9 | ||
|
|
e7c060466d | ||
|
|
340ab9f238 | ||
|
|
1dcc887a62 | ||
|
|
67cf287c03 | ||
|
|
440cbc4c1f | ||
|
|
8d04c33adf | ||
|
|
8a00badf45 | ||
|
|
a76ec75933 | ||
|
|
240091516a | ||
|
|
1127374edd | ||
|
|
a18b62cf6b | ||
|
|
98553f83e3 | ||
|
|
1d9c93a793 | ||
|
|
fb2826f92e | ||
|
|
236e000398 | ||
|
|
8c603a6f8b | ||
|
|
45a42396ab | ||
|
|
4479614754 | ||
|
|
e1a8886d32 | ||
|
|
23b61b080e | ||
|
|
2f14c6cd69 | ||
|
|
964c16f1a4 | ||
|
|
b6f1b4db2f | ||
|
|
482c674a0f | ||
|
|
16e6452b8c | ||
|
|
c36ac53445 | ||
|
|
eabb5bdf7d | ||
|
|
e5da6d3183 | ||
|
|
3345eb32c5 | ||
|
|
1d8d30e5bb | ||
|
|
4a249a0745 | ||
|
|
d121cfdbda | ||
|
|
3eec441abe | ||
|
|
22c061c06c | ||
|
|
7f85d92b85 | ||
|
|
dc98a3b397 | ||
|
|
c458f99dd4 | ||
|
|
70aec7fa76 | ||
|
|
5c91ab91fb | ||
|
|
62d86aba14 | ||
|
|
b40467c7e2 | ||
|
|
f955f0d133 | ||
|
|
a8111de3ef | ||
|
|
9800fb7702 | ||
|
|
17027ff5ca | ||
|
|
9c963b50e6 | ||
|
|
bf5de3cb50 | ||
|
|
2c6bed27aa | ||
|
|
d05ab91d11 | ||
|
|
7748b60ff5 | ||
|
|
e4cb84bc02 | ||
|
|
005e4e3ea8 | ||
|
|
5e3ff0787b | ||
|
|
51798edb23 | ||
|
|
6e0960c3f5 | ||
|
|
db995ad5bf | ||
|
|
b12f5f8394 | ||
|
|
92986c2ecc | ||
|
|
d4206de8e0 | ||
|
|
4935938bb1 | ||
|
|
2b6b4e363b | ||
|
|
de69377b04 | ||
|
|
fd7ce4976a | ||
|
|
775d1ba061 | ||
|
|
9fc7a83320 | ||
|
|
a754258fad | ||
|
|
a745fd49fa | ||
|
|
01f032017f | ||
|
|
d5c5dbb3bf | ||
|
|
c9a7a85159 | ||
|
|
de7f41a888 | ||
|
|
a0e63faf62 | ||
|
|
341ae994c5 | ||
|
|
81e561bdc9 | ||
|
|
170d1b92eb | ||
|
|
2db0107d12 | ||
|
|
df0211fe29 | ||
|
|
1bf1890084 | ||
|
|
2af5a5adb0 | ||
|
|
fe21485065 | ||
|
|
18ab5382b1 | ||
|
|
f98939103b | ||
|
|
ab36bd3965 | ||
|
|
fb2a8ac9a2 | ||
|
|
06a8f4df02 | ||
|
|
cf88551a56 | ||
|
|
aca18fff99 | ||
|
|
a4860075d2 | ||
|
|
bae716fa83 | ||
|
|
0c87034b17 | ||
|
|
8d5d71199a | ||
|
|
cd36845d56 | ||
|
|
c966dd4f9e | ||
|
|
062e4a2922 | ||
|
|
186693b840 | ||
|
|
ab8352ee66 | ||
|
|
bf2cef7d87 | ||
|
|
a6b6eb4ab3 | ||
|
|
cac6ffcd3c | ||
|
|
08c1d03479 | ||
|
|
a2267d869b | ||
|
|
e303537e0c | ||
|
|
a0c29cc260 | ||
|
|
e7c54376e2 | ||
|
|
9d40c24a16 | ||
|
|
e6b258f418 | ||
|
|
02bbc7c7de | ||
|
|
348b19cb4c | ||
|
|
979d385eea | ||
|
|
71c015c579 | ||
|
|
7805666103 | ||
|
|
be0283b9f2 | ||
|
|
e68150c3ce | ||
|
|
92d8139136 | ||
|
|
35f156a1e1 | ||
|
|
6d359161bb | ||
|
|
53207302f9 | ||
|
|
05d10d8e77 | ||
|
|
f5b79c3623 | ||
|
|
9f55287672 | ||
|
|
69fc7f6852 | ||
|
|
cdbd5bf4f9 | ||
|
|
d0bf1cca60 | ||
|
|
66afdfd119 | ||
|
|
4582f0e8d2 | ||
|
|
21f85a4145 | ||
|
|
53b9045943 | ||
|
|
a268232ed6 | ||
|
|
43e9e1ec36 | ||
|
|
80ec241108 | ||
|
|
8315804c85 | ||
|
|
a9fd600c52 | ||
|
|
b5cac47ba7 | ||
|
|
d91d81b5df | ||
|
|
225886e859 | ||
|
|
cc1d22012c | ||
|
|
fb325a8f24 | ||
|
|
f8943c17c2 | ||
|
|
28bdec2c79 | ||
|
|
6fc7af5ba8 | ||
|
|
3b226038ba | ||
|
|
9d06a5888f | ||
|
|
fb7d0e0e3d | ||
|
|
282f4e5de2 | ||
|
|
9bff64824b | ||
|
|
6723a58780 | ||
|
|
519990e2fb | ||
|
|
fb8eb2a5c3 | ||
|
|
962faa2196 | ||
|
|
dad469297c | ||
|
|
85413f2a65 | ||
|
|
9605a4a9cb | ||
|
|
c2baba50f9 | ||
|
|
c955a5dc08 | ||
|
|
e6a5616543 | ||
|
|
001b6cb801 | ||
|
|
f3e620cd0f | ||
|
|
9ef87e75d5 | ||
|
|
f8933c2743 | ||
|
|
b051f779e6 | ||
|
|
37d58a476c | ||
|
|
78f9debdd4 | ||
|
|
74a590cb76 | ||
|
|
4a04d46118 | ||
|
|
467b008f36 | ||
|
|
6701d0ae0c | ||
|
|
4515d18ddd | ||
|
|
60f29ba6fb | ||
|
|
306f4c14cf | ||
|
|
310dcd7b94 | ||
|
|
008c7c6517 | ||
|
|
c244e7f847 | ||
|
|
bb2e2a5ed6 | ||
|
|
a45c0528da | ||
|
|
0b535b792e | ||
|
|
9fbfcf0ccd | ||
|
|
d123249bd2 | ||
|
|
4376476ec4 | ||
|
|
9be61a1245 | ||
|
|
e2e831e322 | ||
|
|
21d585d586 | ||
|
|
0a5144fc1d | ||
|
|
64ca080bb8 | ||
|
|
b85621e9a7 | ||
|
|
e915d7d1d0 | ||
|
|
ae63f64771 | ||
|
|
63dfd95aa2 | ||
|
|
c9f65cf1c2 | ||
|
|
c42e3c6837 | ||
|
|
3167a0dbc0 | ||
|
|
909bd877c9 | ||
|
|
4d0fdba97a | ||
|
|
d2550a525b | ||
|
|
258512fef2 | ||
|
|
9546ee37e5 | ||
|
|
728bc2c632 | ||
|
|
0307c71945 | ||
|
|
208b1f7fa3 | ||
|
|
2f2128ac48 | ||
|
|
c11be72ead | ||
|
|
f5f5913a29 | ||
|
|
af37e12de4 | ||
|
|
0827ff7687 | ||
|
|
3765c31163 | ||
|
|
84a1abd357 | ||
|
|
14f20e644e | ||
|
|
1f960d5761 | ||
|
|
32099d8f49 | ||
|
|
392b880b52 | ||
|
|
3f49938b79 | ||
|
|
a1d4dff99d | ||
|
|
04231c86db | ||
|
|
7265567561 | ||
|
|
0d70373d95 | ||
|
|
53687ae659 | ||
|
|
46db8b7803 | ||
|
|
7cd04c088c | ||
|
|
51b4df7a1f | ||
|
|
b92b725fe2 | ||
|
|
1c1d1ddbec | ||
|
|
5224347c39 | ||
|
|
27011d0239 | ||
|
|
bae2c90bae | ||
|
|
ae1f0bb107 | ||
|
|
405ed2e736 | ||
|
|
17299e469a | ||
|
|
3c43ce4e06 | ||
|
|
00a6772507 | ||
|
|
be3690c22b | ||
|
|
d1f9c77afd | ||
|
|
fee4e7d8e2 | ||
|
|
30d584afd1 | ||
|
|
0a95f74f11 | ||
|
|
6e27bee2ca | ||
|
|
f6f15f58ee | ||
|
|
6f50ddf710 | ||
|
|
5fc934d859 | ||
|
|
8ccdf3b32b | ||
|
|
a5eb02d178 | ||
|
|
dd857a2c7a | ||
|
|
2013963e0b | ||
|
|
48ee0821d3 | ||
|
|
99b61143f6 | ||
|
|
2db19008fb | ||
|
|
514d56d1ae | ||
|
|
5c57c9c0b2 | ||
|
|
7733265fa2 | ||
|
|
61c68f7b8f | ||
|
|
5218b4eea2 | ||
|
|
e1455057e7 | ||
|
|
7a759c903b | ||
|
|
61758c07d2 | ||
|
|
bf830b2dd5 | ||
|
|
6704c0aaec | ||
|
|
6e60a00d69 | ||
|
|
0bf34d4d6f | ||
|
|
19473af401 | ||
|
|
51aad52e6c | ||
|
|
3d2ed363aa | ||
|
|
cc6a5dc29a | ||
|
|
7c9720e22b | ||
|
|
c3b8c96db6 | ||
|
|
93cb60b24e | ||
|
|
5e4a80e5d0 | ||
|
|
79918a7939 | ||
|
|
d09421230b | ||
|
|
1234533c67 | ||
|
|
7f0c0aea94 | ||
|
|
d9dcbb68b7 | ||
|
|
98fba1eefe | ||
|
|
bad03b1e76 | ||
|
|
fcfafebb29 | ||
|
|
47e82b02ed | ||
|
|
a463d66c80 | ||
|
|
337da13ba7 | ||
|
|
4a3453999a | ||
|
|
58758de93d | ||
|
|
b4a8acc584 | ||
|
|
08f89690e9 | ||
|
|
f02899d38d | ||
|
|
86583f1121 | ||
|
|
26cbd71099 | ||
|
|
500ce7a02a | ||
|
|
6d8ceed10e | ||
|
|
68d65f727f | ||
|
|
f165785003 | ||
|
|
8e31c93119 | ||
|
|
4974c08f7d | ||
|
|
fa90ba976c | ||
|
|
35c3d8dfbc | ||
|
|
ee23815aff | ||
|
|
7c946ae3db | ||
|
|
3926f5c55c | ||
|
|
fdc03d4bf3 | ||
|
|
24f0aed8a7 | ||
|
|
00d2f577df | ||
|
|
c35fc71bc5 | ||
|
|
1b6d57c3f3 | ||
|
|
d089a96163 | ||
|
|
0b3fe3d60c | ||
|
|
0eeb184b6a | ||
|
|
8e7edce616 | ||
|
|
754201b3d0 | ||
|
|
925401b4e1 | ||
|
|
8368ea4094 | ||
|
|
e8a6fb24ae | ||
|
|
311b7a72dc | ||
|
|
aa496def53 | ||
|
|
aea4375255 | ||
|
|
9ab0a0179d | ||
|
|
3db613dab5 | ||
|
|
de5ca79805 | ||
|
|
aede3bb5ba | ||
|
|
408f84aea6 | ||
|
|
92c07aaf54 | ||
|
|
f405174fcf | ||
|
|
8c125d2553 | ||
|
|
fb8fca4c64 | ||
|
|
dc0c055518 | ||
|
|
09349cb1e7 | ||
|
|
ca29adb0cb | ||
|
|
1617bbbe71 | ||
|
|
de1dd53186 | ||
|
|
58672dfab6 | ||
|
|
4b5629d1c8 | ||
|
|
4ddc3f14ed | ||
|
|
400a8aec89 | ||
|
|
51489a75ce | ||
|
|
09772eeda0 | ||
|
|
78907d08cd | ||
|
|
d0a0d280a1 | ||
|
|
5d77ed3677 | ||
|
|
f68ee6ba67 | ||
|
|
a01560cfa1 | ||
|
|
7e06ce8eeb | ||
|
|
ccc0e3dbb2 | ||
|
|
bd48e87eeb | ||
|
|
e6bd03fe98 | ||
|
|
9252d835b8 | ||
|
|
35b5f8dcdc | ||
|
|
97518544ee | ||
|
|
1c934b474a | ||
|
|
9d1d396a9b | ||
|
|
c38ba1daa8 | ||
|
|
8727d321f3 | ||
|
|
9918f8868e | ||
|
|
3dcf85caef | ||
|
|
e437ae1f2f | ||
|
|
17ebbdd966 | ||
|
|
de0bd37a66 | ||
|
|
412587ad41 | ||
|
|
941907ed4e | ||
|
|
91fbc64327 | ||
|
|
79ff093b30 | ||
|
|
ff80d4f406 | ||
|
|
c846cd187c | ||
|
|
9a43a47e6a | ||
|
|
9f2fead651 | ||
|
|
5a15c632ad | ||
|
|
3acc9aba8b | ||
|
|
8a61cdf4f2 | ||
|
|
ed47ceb1e4 | ||
|
|
4dbdb1811b | ||
|
|
d921f6d2b4 | ||
|
|
9d3fab8e4d | ||
|
|
a9630f3ec7 | ||
|
|
c2120f62cf | ||
|
|
19cf645224 | ||
|
|
6b1c058436 | ||
|
|
54b4056eb3 | ||
|
|
82e4763588 | ||
|
|
831a9a8a57 | ||
|
|
aa54da6d67 | ||
|
|
930736bdb0 | ||
|
|
668dcf4799 | ||
|
|
50a5854b25 | ||
|
|
441e043bff | ||
|
|
d383b2bf3c | ||
|
|
d7903631f5 | ||
|
|
10cb964c96 | ||
|
|
c519094313 | ||
|
|
e32c2d82a5 | ||
|
|
f7b9d7a64b | ||
|
|
89932fa0b2 | ||
|
|
a90928766b | ||
|
|
9d50f1b8a2 | ||
|
|
72464afb2e | ||
|
|
64af516dfd | ||
|
|
70cd4dd9d9 | ||
|
|
b7266e647c | ||
|
|
77c3146829 | ||
|
|
4545d55d30 | ||
|
|
a7b158c7fa | ||
|
|
d39c55e941 | ||
|
|
821173f6c4 | ||
|
|
635b7a6a4d | ||
|
|
04b1a45416 | ||
|
|
d23006ce03 | ||
|
|
8bb911bc91 | ||
|
|
249733c768 | ||
|
|
d83a938870 | ||
|
|
593e376cf2 | ||
|
|
fd4fe2c2ad | ||
|
|
281fa67b8e | ||
|
|
a8fd27aec3 | ||
|
|
139b5ae20c | ||
|
|
891f826143 | ||
|
|
0e2fb1d1a3 | ||
|
|
96170e43c0 | ||
|
|
8fde970b6b | ||
|
|
38cb577fc6 | ||
|
|
8ebe045846 | ||
|
|
62dc5c0306 | ||
|
|
6ad8d29fcd | ||
|
|
d83a88d5d9 | ||
|
|
a7cc48dcda | ||
|
|
e2a7dc9256 | ||
|
|
0364933e8b | ||
|
|
3202ff4b3f | ||
|
|
553204e613 | ||
|
|
fe8348c03a | ||
|
|
30021f8ede | ||
|
|
f3349388d0 | ||
|
|
449a89c214 | ||
|
|
5428376662 | ||
|
|
5493e2c96d | ||
|
|
2f8657f122 | ||
|
|
aa97d2fe03 | ||
|
|
28389de93e | ||
|
|
7c415c7cd9 | ||
|
|
f6b2abee81 | ||
|
|
2ef7232959 | ||
|
|
2991f60368 | ||
|
|
85ec3b1901 | ||
|
|
0c639abe17 | ||
|
|
50ed9b9e0e | ||
|
|
13a164dd63 | ||
|
|
6b0b13e4e7 | ||
|
|
c188952de2 | ||
|
|
a3b91f924d | ||
|
|
d8688cf8b1 | ||
|
|
40378afbf8 | ||
|
|
84e21434b3 | ||
|
|
d27210794c | ||
|
|
5a90a84685 | ||
|
|
6fbbc85df7 | ||
|
|
a66c230058 | ||
|
|
19db0353a9 | ||
|
|
88e5581d04 | ||
|
|
bf88d9bb1c | ||
|
|
d51b35f61b | ||
|
|
220480b627 | ||
|
|
ef78d2af06 | ||
|
|
7deed00def | ||
|
|
4e093a8e2a | ||
|
|
0b73b4842f |
@@ -83,6 +83,7 @@ github:
|
||||
- cypress-matrix (5, chrome)
|
||||
- dependency-review
|
||||
- frontend-build
|
||||
- playwright-tests (chromium)
|
||||
- pre-commit (current)
|
||||
- pre-commit (previous)
|
||||
- test-mysql
|
||||
|
||||
@@ -3,14 +3,3 @@
|
||||
For complete documentation on using GitHub Codespaces with Apache Superset, please see:
|
||||
|
||||
**[Setting up a Development Environment - GitHub Codespaces](https://superset.apache.org/docs/contributing/development#github-codespaces-cloud-development)**
|
||||
|
||||
## Pre-installed Development Environment
|
||||
|
||||
When you create a new Codespace from this repository, it automatically:
|
||||
|
||||
1. **Creates a Python virtual environment** using `uv venv`
|
||||
2. **Installs all development dependencies** via `uv pip install -r requirements/development.txt`
|
||||
3. **Sets up pre-commit hooks** with `pre-commit install`
|
||||
4. **Activates the virtual environment** automatically in all terminals
|
||||
|
||||
The virtual environment is located at `/workspaces/{repository-name}/.venv` and is automatically activated through environment variables set in the devcontainer configuration.
|
||||
|
||||
19
.devcontainer/default/devcontainer.json
Normal file
19
.devcontainer/default/devcontainer.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
// Extend the base configuration
|
||||
"extends": "../devcontainer-base.json",
|
||||
|
||||
"name": "Apache Superset Development (Default)",
|
||||
|
||||
// Forward ports for development
|
||||
"forwardPorts": [9001],
|
||||
"portsAttributes": {
|
||||
"9001": {
|
||||
"label": "Superset (via Webpack Dev Server)",
|
||||
"onAutoForward": "notify",
|
||||
"visibility": "public"
|
||||
}
|
||||
},
|
||||
|
||||
// Auto-start Superset on Codespace resume
|
||||
"postStartCommand": ".devcontainer/start-superset.sh"
|
||||
}
|
||||
39
.devcontainer/devcontainer-base.json
Normal file
39
.devcontainer/devcontainer-base.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "Apache Superset Development",
|
||||
// Keep this in sync with the base image in Dockerfile (ARG PY_VER)
|
||||
// Using the same base as Dockerfile, but non-slim for dev tools
|
||||
"image": "python:3.11.13-bookworm",
|
||||
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||
"moby": true,
|
||||
"dockerDashComposeVersion": "v2"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "20"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/git:1": {},
|
||||
"ghcr.io/devcontainers/features/common-utils:2": {
|
||||
"configureZshAsDefaultShell": true
|
||||
},
|
||||
"ghcr.io/devcontainers/features/sshd:1": {
|
||||
"version": "latest"
|
||||
}
|
||||
},
|
||||
|
||||
// Run commands after container is created
|
||||
"postCreateCommand": "chmod +x .devcontainer/setup-dev.sh && .devcontainer/setup-dev.sh",
|
||||
|
||||
// VS Code customizations
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"charliermarsh.ruff",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,76 +3,30 @@
|
||||
|
||||
echo "🔧 Setting up Superset development environment..."
|
||||
|
||||
# System dependencies and uv are now pre-installed in the Docker image
|
||||
# This speeds up Codespace creation significantly!
|
||||
# The universal image has most tools, just need Superset-specific libs
|
||||
echo "📦 Installing Superset-specific dependencies..."
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
libsasl2-dev \
|
||||
libldap2-dev \
|
||||
libpq-dev \
|
||||
tmux \
|
||||
gh
|
||||
|
||||
# Create virtual environment using uv
|
||||
echo "🐍 Creating Python virtual environment..."
|
||||
if ! uv venv; then
|
||||
echo "❌ Failed to create virtual environment"
|
||||
exit 1
|
||||
fi
|
||||
# Install uv for fast Python package management
|
||||
echo "📦 Installing uv..."
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
|
||||
# Install Python dependencies
|
||||
echo "📦 Installing Python dependencies..."
|
||||
if ! uv pip install -r requirements/development.txt; then
|
||||
echo "❌ Failed to install Python dependencies"
|
||||
echo "💡 You may need to run this manually after the Codespace starts"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install pre-commit hooks
|
||||
echo "🪝 Installing pre-commit hooks..."
|
||||
if source .venv/bin/activate && pre-commit install; then
|
||||
echo "✅ Pre-commit hooks installed"
|
||||
else
|
||||
echo "⚠️ Pre-commit hooks installation failed (non-critical)"
|
||||
fi
|
||||
# Add cargo/bin to PATH for uv
|
||||
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.bashrc
|
||||
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.zshrc
|
||||
|
||||
# Install Claude Code CLI via npm
|
||||
echo "🤖 Installing Claude Code..."
|
||||
if npm install -g @anthropic-ai/claude-code; then
|
||||
echo "✅ Claude Code installed"
|
||||
else
|
||||
echo "⚠️ Claude Code installation failed (non-critical)"
|
||||
fi
|
||||
npm install -g @anthropic-ai/claude-code
|
||||
|
||||
# Make the start script executable
|
||||
chmod +x .devcontainer/start-superset.sh
|
||||
|
||||
# Add bashrc additions for automatic venv activation
|
||||
echo "🔧 Setting up automatic environment activation..."
|
||||
if [ -f ~/.bashrc ]; then
|
||||
# Check if we've already added our additions
|
||||
if ! grep -q "Superset Codespaces environment setup" ~/.bashrc; then
|
||||
echo "" >> ~/.bashrc
|
||||
cat .devcontainer/bashrc-additions >> ~/.bashrc
|
||||
echo "✅ Added automatic venv activation to ~/.bashrc"
|
||||
else
|
||||
echo "✅ Bashrc additions already present"
|
||||
fi
|
||||
else
|
||||
# Create bashrc if it doesn't exist
|
||||
cat .devcontainer/bashrc-additions > ~/.bashrc
|
||||
echo "✅ Created ~/.bashrc with automatic venv activation"
|
||||
fi
|
||||
|
||||
# Also add to zshrc since that's the default shell
|
||||
if [ -f ~/.zshrc ] || [ -n "$ZSH_VERSION" ]; then
|
||||
if ! grep -q "Superset Codespaces environment setup" ~/.zshrc; then
|
||||
echo "" >> ~/.zshrc
|
||||
cat .devcontainer/bashrc-additions >> ~/.zshrc
|
||||
echo "✅ Added automatic venv activation to ~/.zshrc"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "✅ Development environment setup complete!"
|
||||
echo ""
|
||||
echo "📝 The virtual environment will be automatically activated in new terminals"
|
||||
echo ""
|
||||
echo "🔄 To activate in this terminal, run:"
|
||||
echo " source ~/.bashrc"
|
||||
echo ""
|
||||
echo "🚀 To start Superset:"
|
||||
echo " start-superset"
|
||||
echo ""
|
||||
echo "🚀 Run '.devcontainer/start-superset.sh' to start Superset"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#!/bin/bash
|
||||
# Startup script for Superset in Codespaces
|
||||
|
||||
# Log to a file for debugging
|
||||
LOG_FILE="/tmp/superset-startup.log"
|
||||
echo "[$(date)] Starting Superset startup script" >> "$LOG_FILE"
|
||||
echo "[$(date)] User: $(whoami), PWD: $(pwd)" >> "$LOG_FILE"
|
||||
|
||||
echo "🚀 Starting Superset in Codespaces..."
|
||||
echo "🌐 Frontend will be available at port 9001"
|
||||
|
||||
# Check if MCP is enabled
|
||||
if [ "$ENABLE_MCP" = "true" ]; then
|
||||
echo "🤖 MCP Service will be available at port 5008"
|
||||
fi
|
||||
|
||||
# Find the workspace directory (Codespaces clones as 'superset', not 'superset-2')
|
||||
WORKSPACE_DIR=$(find /workspaces -maxdepth 1 -name "superset*" -type d | head -1)
|
||||
if [ -n "$WORKSPACE_DIR" ]; then
|
||||
@@ -18,71 +18,32 @@ else
|
||||
echo "📁 Using current directory: $(pwd)"
|
||||
fi
|
||||
|
||||
# Wait for Docker to be available
|
||||
echo "⏳ Waiting for Docker to start..."
|
||||
echo "[$(date)] Waiting for Docker..." >> "$LOG_FILE"
|
||||
max_attempts=30
|
||||
attempt=0
|
||||
while ! docker info > /dev/null 2>&1; do
|
||||
if [ $attempt -eq $max_attempts ]; then
|
||||
echo "❌ Docker failed to start after $max_attempts attempts"
|
||||
echo "[$(date)] Docker failed to start after $max_attempts attempts" >> "$LOG_FILE"
|
||||
echo "🔄 Please restart the Codespace or run this script manually later"
|
||||
exit 1
|
||||
fi
|
||||
echo " Attempt $((attempt + 1))/$max_attempts..."
|
||||
echo "[$(date)] Docker check attempt $((attempt + 1))/$max_attempts" >> "$LOG_FILE"
|
||||
sleep 2
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
echo "✅ Docker is ready!"
|
||||
echo "[$(date)] Docker is ready" >> "$LOG_FILE"
|
||||
|
||||
# Check if Superset containers are already running
|
||||
if docker ps | grep -q "superset"; then
|
||||
echo "✅ Superset containers are already running!"
|
||||
echo ""
|
||||
echo "🌐 To access Superset:"
|
||||
echo " 1. Click the 'Ports' tab at the bottom of VS Code"
|
||||
echo " 2. Find port 9001 and click the globe icon to open"
|
||||
echo " 3. Wait 10-20 minutes for initial startup"
|
||||
echo ""
|
||||
echo "📝 Login credentials: admin/admin"
|
||||
exit 0
|
||||
# Check if docker is running
|
||||
if ! docker info > /dev/null 2>&1; then
|
||||
echo "⏳ Waiting for Docker to start..."
|
||||
sleep 5
|
||||
fi
|
||||
|
||||
# Clean up any existing containers
|
||||
echo "🧹 Cleaning up existing containers..."
|
||||
docker-compose -f docker-compose-light.yml down
|
||||
docker-compose -f docker-compose-light.yml --profile mcp down
|
||||
|
||||
# Start services
|
||||
echo "🏗️ Starting Superset in background (daemon mode)..."
|
||||
echo "🏗️ Building and starting services..."
|
||||
echo ""
|
||||
echo "📝 Once started, login with:"
|
||||
echo " Username: admin"
|
||||
echo " Password: admin"
|
||||
echo ""
|
||||
echo "📋 Running in foreground with live logs (Ctrl+C to stop)..."
|
||||
|
||||
# Start in detached mode
|
||||
docker-compose -f docker-compose-light.yml up -d
|
||||
|
||||
echo ""
|
||||
echo "✅ Docker Compose started successfully!"
|
||||
echo ""
|
||||
echo "📋 Important information:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "⏱️ Initial startup takes 10-20 minutes"
|
||||
echo "🌐 Check the 'Ports' tab for your Superset URL (port 9001)"
|
||||
echo "👤 Login: admin / admin"
|
||||
echo ""
|
||||
echo "📊 Useful commands:"
|
||||
echo " docker-compose -f docker-compose-light.yml logs -f # Follow logs"
|
||||
echo " docker-compose -f docker-compose-light.yml ps # Check status"
|
||||
echo " docker-compose -f docker-compose-light.yml down # Stop services"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "💤 Keeping terminal open for 60 seconds to test persistence..."
|
||||
sleep 60
|
||||
echo "✅ Test complete - check if this terminal is still visible!"
|
||||
|
||||
# Show final status
|
||||
docker-compose -f docker-compose-light.yml ps
|
||||
# Run docker-compose and capture exit code
|
||||
if [ "$ENABLE_MCP" = "true" ]; then
|
||||
echo "🤖 Starting with MCP Service enabled..."
|
||||
docker-compose -f docker-compose-light.yml --profile mcp up
|
||||
else
|
||||
docker-compose -f docker-compose-light.yml up
|
||||
fi
|
||||
EXIT_CODE=$?
|
||||
|
||||
# If it failed, provide helpful instructions
|
||||
|
||||
29
.devcontainer/with-mcp/devcontainer.json
Normal file
29
.devcontainer/with-mcp/devcontainer.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
// Extend the base configuration
|
||||
"extends": "../devcontainer-base.json",
|
||||
|
||||
"name": "Apache Superset Development with MCP",
|
||||
|
||||
// Forward ports for development
|
||||
"forwardPorts": [9001, 5008],
|
||||
"portsAttributes": {
|
||||
"9001": {
|
||||
"label": "Superset (via Webpack Dev Server)",
|
||||
"onAutoForward": "notify",
|
||||
"visibility": "public"
|
||||
},
|
||||
"5008": {
|
||||
"label": "MCP Service (Model Context Protocol)",
|
||||
"onAutoForward": "notify",
|
||||
"visibility": "private"
|
||||
}
|
||||
},
|
||||
|
||||
// Auto-start Superset with MCP on Codespace resume
|
||||
"postStartCommand": "ENABLE_MCP=true .devcontainer/start-superset.sh",
|
||||
|
||||
// Environment variables
|
||||
"containerEnv": {
|
||||
"ENABLE_MCP": "true"
|
||||
}
|
||||
}
|
||||
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
@@ -20,7 +20,7 @@
|
||||
|
||||
# Notify PMC members of changes to GitHub Actions
|
||||
|
||||
/.github/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar
|
||||
/.github/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar @sadpandajoe
|
||||
|
||||
# Notify PMC members of changes to required GitHub Actions
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
# Notify PMC members of changes to extension-related files
|
||||
|
||||
/docs/developer_portal/extensions/ @michael-s-molina @villebro @rusackas
|
||||
/superset-core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset-extensions-cli/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset/core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
|
||||
70
.github/dependabot.yml
vendored
70
.github/dependabot.yml
vendored
@@ -5,7 +5,7 @@ updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
ignore:
|
||||
@@ -18,7 +18,7 @@ updates:
|
||||
- dependency-name: "jest-environment-jsdom"
|
||||
directory: "/superset-frontend/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -40,21 +40,21 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: ".github/actions"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
versioning-strategy: increase
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/docs/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
versioning-strategy: increase
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-websocket/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -63,7 +63,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-websocket/utils/client-ws-app/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -75,7 +75,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-calendar/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -85,7 +85,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-histogram/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -95,7 +95,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-partition/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -105,7 +105,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-world-map/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -115,7 +115,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-pivot-table/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -125,7 +125,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-chord/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -135,7 +135,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-horizon/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -145,7 +145,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-rose/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -155,7 +155,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-preset-chart-deckgl/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -165,7 +165,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-table/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -175,7 +175,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-country-map/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -185,7 +185,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-map-box/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -195,7 +195,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-sankey/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -205,7 +205,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-preset-chart-nvd3/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -215,7 +215,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-word-cloud/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -225,7 +225,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-event-flow/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -235,7 +235,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-paired-t-test/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -245,7 +245,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -255,7 +255,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-echarts/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -265,7 +265,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/preset-chart-xy/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -275,7 +275,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-heatmap/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -285,7 +285,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-parallel-coordinates/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -295,7 +295,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-sunburst/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -305,7 +305,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-handlebars/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -315,7 +315,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/packages/generator-superset/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -325,7 +325,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/packages/superset-ui-chart-controls/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -339,7 +339,7 @@ updates:
|
||||
- dependency-name: "react-markdown"
|
||||
- dependency-name: "remark-gfm"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -349,7 +349,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/packages/superset-ui-demo/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -359,7 +359,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/packages/superset-ui-switchboard/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
|
||||
23
.github/workflows/bashlib.sh
vendored
23
.github/workflows/bashlib.sh
vendored
@@ -195,6 +195,7 @@ playwright-install() {
|
||||
|
||||
playwright-run() {
|
||||
local APP_ROOT=$1
|
||||
local TEST_PATH=$2
|
||||
|
||||
# Start Flask from the project root (same as Cypress)
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
@@ -238,8 +239,26 @@ playwright-run() {
|
||||
|
||||
say "::group::Run Playwright tests"
|
||||
echo "Running Playwright with baseURL: ${PLAYWRIGHT_BASE_URL}"
|
||||
npx playwright test auth/login --reporter=github --output=playwright-results
|
||||
local status=$?
|
||||
if [ -n "$TEST_PATH" ]; then
|
||||
# Check if there are any test files in the specified path
|
||||
if ! find "playwright/tests/${TEST_PATH}" -name "*.spec.ts" -type f 2>/dev/null | grep -q .; then
|
||||
echo "No test files found in ${TEST_PATH} - skipping test run"
|
||||
say "::endgroup::"
|
||||
kill $flaskProcessId
|
||||
return 0
|
||||
fi
|
||||
echo "Running tests: ${TEST_PATH}"
|
||||
# Set INCLUDE_EXPERIMENTAL=true to allow experimental tests to run
|
||||
export INCLUDE_EXPERIMENTAL=true
|
||||
npx playwright test "${TEST_PATH}" --output=playwright-results
|
||||
local status=$?
|
||||
# Unset to prevent leaking into subsequent commands
|
||||
unset INCLUDE_EXPERIMENTAL
|
||||
else
|
||||
echo "Running all required tests (experimental/ excluded via playwright.config.ts)"
|
||||
npx playwright test --output=playwright-results
|
||||
local status=$?
|
||||
fi
|
||||
say "::endgroup::"
|
||||
|
||||
# After job is done, print out Flask log for debugging
|
||||
|
||||
4
.github/workflows/bump-python-package.yml
vendored
4
.github/workflows/bump-python-package.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: master
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
uses: ./.github/actions/setup-supersetbot/
|
||||
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
|
||||
2
.github/workflows/cancel_duplicates.yml
vendored
2
.github/workflows/cancel_duplicates.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
if: steps.check_queued.outputs.count >= 20
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Cancel duplicate workflow runs
|
||||
if: steps.check_queued.outputs.count >= 20
|
||||
|
||||
2
.github/workflows/check-python-deps.yml
vendored
2
.github/workflows/check-python-deps.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -25,9 +25,9 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Check and notify
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
script: |
|
||||
|
||||
4
.github/workflows/claude.yml
vendored
4
.github/workflows/claude.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Comment access denied
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const message = `👋 Hi @${{ github.event.comment.user.login || github.event.review.user.login || github.event.issue.user.login }}!
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
|
||||
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -53,6 +53,6 @@ jobs:
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
4
.github/workflows/dependency-review.yml
vendored
4
.github/workflows/dependency-review.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: "Dependency Review"
|
||||
uses: actions/dependency-review-action@v4
|
||||
continue-on-error: true
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
|
||||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
|
||||
4
.github/workflows/embedded-sdk-release.yml
vendored
4
.github/workflows/embedded-sdk-release.yml
vendored
@@ -28,8 +28,8 @@ jobs:
|
||||
run:
|
||||
working-directory: superset-embedded-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: './superset-embedded-sdk/.nvmrc'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
4
.github/workflows/embedded-sdk-test.yml
vendored
4
.github/workflows/embedded-sdk-test.yml
vendored
@@ -18,8 +18,8 @@ jobs:
|
||||
run:
|
||||
working-directory: superset-embedded-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: './superset-embedded-sdk/.nvmrc'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
4
.github/workflows/ephemeral-env-pr-close.yml
vendored
4
.github/workflows/ephemeral-env-pr-close.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
- name: Comment (success)
|
||||
if: steps.describe-services.outputs.active == 'true'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{github.token}}
|
||||
script: |
|
||||
|
||||
20
.github/workflows/ephemeral-env.yml
vendored
20
.github/workflows/ephemeral-env.yml
vendored
@@ -63,7 +63,7 @@ jobs:
|
||||
- name: Get event SHA
|
||||
id: get-sha
|
||||
if: steps.eval-label.outputs.result == 'up'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
core.setOutput("sha", prSha);
|
||||
|
||||
- name: Looking for feature flags in PR description
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
id: eval-feature-flags
|
||||
if: steps.eval-label.outputs.result == 'up'
|
||||
with:
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
return results;
|
||||
|
||||
- name: Reply with confirmation comment
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
if: steps.eval-label.outputs.result == 'up'
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -160,7 +160,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ needs.ephemeral-env-label.outputs.sha }} : ${{steps.get-sha.outputs.sha}} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.ephemeral-env-label.outputs.sha }}
|
||||
persist-credentials: false
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
--extra-flags "--build-arg INCLUDE_CHROMIUM=false"
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
@@ -220,12 +220,12 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
@@ -248,7 +248,7 @@ jobs:
|
||||
|
||||
- name: Fail on missing container image
|
||||
if: steps.check-image.outcome == 'failure'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
script: |
|
||||
@@ -318,7 +318,7 @@ jobs:
|
||||
echo "ip=$(aws ec2 describe-network-interfaces --network-interface-ids ${{ steps.get-eni.outputs.eni }} | jq -r '.NetworkInterfaces | first | .Association.PublicIp')" >> $GITHUB_OUTPUT
|
||||
- name: Comment (success)
|
||||
if: ${{ success() }}
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{github.token}}
|
||||
script: |
|
||||
@@ -331,7 +331,7 @@ jobs:
|
||||
});
|
||||
- name: Comment (failure)
|
||||
if: ${{ failure() }}
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{github.token}}
|
||||
script: |
|
||||
|
||||
2
.github/workflows/generate-FOSSA-report.yml
vendored
2
.github/workflows/generate-FOSSA-report.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -14,10 +14,10 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
|
||||
2
.github/workflows/issue_creation.yml
vendored
2
.github/workflows/issue_creation.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
2
.github/workflows/labeler.yml
vendored
2
.github/workflows/labeler.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
- uses: actions/labeler@v6
|
||||
with:
|
||||
sync-labels: true
|
||||
|
||||
|
||||
2
.github/workflows/latest-release-tag.yml
vendored
2
.github/workflows/latest-release-tag.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/license-check.yml
vendored
2
.github/workflows/license-check.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/no-hold-label.yml
vendored
2
.github/workflows/no-hold-label.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check for 'hold' label
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
|
||||
2
.github/workflows/pr-lint.yml
vendored
2
.github/workflows/pr-lint.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
10
.github/workflows/pre-commit.yml
vendored
10
.github/workflows/pre-commit.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
python-version: ["current", "previous", "next"]
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
echo "HOMEBREW_REPOSITORY=$HOMEBREW_REPOSITORY" >>"${GITHUB_ENV}"
|
||||
brew install norwoodj/tap/helm-docs
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
yarn install --immutable
|
||||
|
||||
- name: Cache pre-commit environments
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
@@ -71,7 +71,9 @@ jobs:
|
||||
GIT_DIFF_EXIT_CODE=$?
|
||||
if [ "${PRE_COMMIT_EXIT_CODE}" -ne 0 ] || [ "${GIT_DIFF_EXIT_CODE}" -ne 0 ]; then
|
||||
if [ "${PRE_COMMIT_EXIT_CODE}" -ne 0 ]; then
|
||||
echo "❌ Pre-commit check failed (exit code: ${EXIT_CODE})."
|
||||
echo "❌ Pre-commit check failed (exit code: ${PRE_COMMIT_EXIT_CODE})."
|
||||
echo "🔍 Modified files:"
|
||||
git diff --name-only
|
||||
else
|
||||
echo "❌ Git working directory is dirty."
|
||||
echo "📌 This likely means that pre-commit made changes that were not committed."
|
||||
|
||||
2
.github/workflows/prefer-typescript.yml
vendored
2
.github/workflows/prefer-typescript.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
name: Bump version and publish package(s)
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
# pulls all commits (needed for lerna / semantic release to correctly version)
|
||||
fetch-depth: 0
|
||||
@@ -42,13 +42,13 @@ jobs:
|
||||
|
||||
- name: Install Node.js
|
||||
if: env.HAS_TAGS
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
|
||||
- name: Cache npm
|
||||
if: env.HAS_TAGS
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
- name: Cache npm
|
||||
if: env.HAS_TAGS
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: npm-cache # use this to check for `cache-hit` (`steps.npm-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
|
||||
|
||||
4
.github/workflows/showtime-trigger.yml
vendored
4
.github/workflows/showtime-trigger.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
steps:
|
||||
- name: Security Check - Authorize Maintainers Only
|
||||
id: auth
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
|
||||
- name: Checkout PR code (only if build needed)
|
||||
if: steps.auth.outputs.authorized == 'true' && steps.check.outputs.build_needed == 'true'
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ steps.check.outputs.target_sha }}
|
||||
persist-credentials: false
|
||||
|
||||
2
.github/workflows/superset-app-cli.yml
vendored
2
.github/workflows/superset-app-cli.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
with:
|
||||
run: testdata
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install npm dependencies
|
||||
|
||||
@@ -30,13 +30,13 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
ref: master
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install eyes-storybook dependencies
|
||||
|
||||
4
.github/workflows/superset-docs-deploy.yml
vendored
4
.github/workflows/superset-docs-deploy.yml
vendored
@@ -31,12 +31,12 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: './docs/.nvmrc'
|
||||
- name: Setup Python
|
||||
|
||||
26
.github/workflows/superset-docs-verify.yml
vendored
26
.github/workflows/superset-docs-verify.yml
vendored
@@ -18,15 +18,17 @@ jobs:
|
||||
name: Link Checking
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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@v1.11.0
|
||||
- uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3
|
||||
continue-on-error: true # This will make the job advisory (non-blocking, no red X)
|
||||
with:
|
||||
paths: "**/*.md, **/*.mdx, !superset-frontend/CHANGELOG.md"
|
||||
paths: "**/*.md, **/*.mdx"
|
||||
linksToSkip: >-
|
||||
^https://github.com/apache/(superset|incubator-superset)/(pull|issue)/\d+,
|
||||
^https://github.com/apache/(superset|incubator-superset)/(pull|issues)/\d+,
|
||||
^https://github.com/apache/(superset|incubator-superset)/commit/[a-f0-9]+,
|
||||
superset-frontend/.*CHANGELOG\.md,
|
||||
http://localhost:8088/,
|
||||
http://127.0.0.1:3000/,
|
||||
http://localhost:9001/,
|
||||
@@ -41,12 +43,12 @@ jobs:
|
||||
http://theiconic.com.au/,
|
||||
https://dev.mysql.com/doc/refman/5.7/en/innodb-limits.html,
|
||||
^https://img\.shields\.io/.*,
|
||||
https://vkusvill.ru/
|
||||
https://www.linkedin.com/in/mark-thomas-b16751158/
|
||||
https://theiconic.com.au/
|
||||
https://wattbewerb.de/
|
||||
https://timbr.ai/
|
||||
https://opensource.org/license/apache-2-0
|
||||
https://vkusvill.ru/,
|
||||
https://www.linkedin.com/in/mark-thomas-b16751158/,
|
||||
https://theiconic.com.au/,
|
||||
https://wattbewerb.de/,
|
||||
https://timbr.ai/,
|
||||
https://opensource.org/license/apache-2-0,
|
||||
https://www.plaidcloud.com/
|
||||
build-deploy:
|
||||
name: Build & Deploy
|
||||
@@ -56,12 +58,12 @@ jobs:
|
||||
working-directory: docs
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: './docs/.nvmrc'
|
||||
- name: yarn install
|
||||
|
||||
125
.github/workflows/superset-e2e.yml
vendored
125
.github/workflows/superset-e2e.yml
vendored
@@ -69,21 +69,21 @@ jobs:
|
||||
# Conditional checkout based on context
|
||||
- name: Checkout for push or pull_request event
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Checkout using ref (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
submodules: recursive
|
||||
- name: Checkout using PR ID (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
run: testdata
|
||||
- name: Setup Node.js
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install npm dependencies
|
||||
@@ -146,8 +146,123 @@ jobs:
|
||||
SAFE_APP_ROOT=${APP_ROOT//\//_}
|
||||
echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
if: failure()
|
||||
with:
|
||||
path: ${{ github.workspace }}/superset-frontend/cypress-base/cypress/screenshots
|
||||
name: cypress-artifact-${{ github.run_id }}-${{ github.job }}-${{ matrix.browser }}-${{ matrix.parallel_id }}--${{ steps.set-safe-app-root.outputs.safe_app_root }}
|
||||
|
||||
playwright-tests:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
browser: ["chromium"]
|
||||
app_root: ["", "/app/prefix"]
|
||||
env:
|
||||
SUPERSET_ENV: development
|
||||
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
|
||||
SUPERSET__SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://superset:superset@127.0.0.1:15432/superset
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
REDIS_PORT: 16379
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_USER: superset
|
||||
POSTGRES_PASSWORD: superset
|
||||
ports:
|
||||
- 15432:5432
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- 16379:6379
|
||||
steps:
|
||||
# -------------------------------------------------------
|
||||
# Conditional checkout based on context (same as Cypress workflow)
|
||||
- name: Checkout for push or pull_request event
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Checkout using ref (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
submodules: recursive
|
||||
- name: Checkout using PR ID (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
submodules: recursive
|
||||
# -------------------------------------------------------
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
- name: Setup postgres
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: setup-postgres
|
||||
- name: Import test data
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: testdata
|
||||
- name: Setup Node.js
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install npm dependencies
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: npm-install
|
||||
- name: Build javascript packages
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: build-instrumented-assets
|
||||
- name: Install Playwright
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: playwright-install
|
||||
- name: Run Playwright (Required Tests)
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=4096"
|
||||
with:
|
||||
run: playwright-run "${{ matrix.app_root }}"
|
||||
- name: Set safe app root
|
||||
if: failure()
|
||||
id: set-safe-app-root
|
||||
run: |
|
||||
APP_ROOT="${{ matrix.app_root }}"
|
||||
SAFE_APP_ROOT=${APP_ROOT//\//_}
|
||||
echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT
|
||||
- name: Upload Playwright Artifacts
|
||||
uses: actions/upload-artifact@v5
|
||||
if: failure()
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/superset-frontend/playwright-results/
|
||||
${{ github.workspace }}/superset-frontend/test-results/
|
||||
name: playwright-artifact-${{ github.run_id }}-${{ github.job }}-${{ matrix.browser }}--${{ steps.set-safe-app-root.outputs.safe_app_root }}
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
working-directory: superset-extensions-cli
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
- name: Upload HTML coverage report
|
||||
if: steps.check.outputs.superset-extensions-cli
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: superset-extensions-cli-coverage-html
|
||||
path: htmlcov/
|
||||
|
||||
20
.github/workflows/superset-frontend.yml
vendored
20
.github/workflows/superset-frontend.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
should-run: ${{ steps.check.outputs.frontend }}
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
- name: Upload Docker Image Artifact
|
||||
if: steps.check.outputs.frontend
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: docker-image
|
||||
path: docker-image.tar.gz
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
"npm run test -- --coverage --shard=${{ matrix.shard }}/8 --coverageReporters=json-summary"
|
||||
|
||||
- name: Upload Coverage Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: coverage-artifacts-${{ matrix.shard }}
|
||||
path: superset-frontend/coverage
|
||||
@@ -101,7 +101,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Download Coverage Artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: coverage-artifacts-*
|
||||
path: coverage/
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
@@ -135,15 +135,15 @@ jobs:
|
||||
run: |
|
||||
docker load < docker-image.tar.gz
|
||||
|
||||
- name: eslint
|
||||
- name: lint
|
||||
run: |
|
||||
docker run --rm $TAG bash -c \
|
||||
"npm i && npm run eslint -- . --quiet"
|
||||
"npm i && npm run lint"
|
||||
|
||||
- name: tsc
|
||||
run: |
|
||||
docker run --rm $TAG bash -c \
|
||||
"npm run plugins:build && npm run type"
|
||||
"npm i && npm run plugins:build && npm run type"
|
||||
|
||||
validate-frontend:
|
||||
needs: frontend-build
|
||||
@@ -151,7 +151,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
|
||||
2
.github/workflows/superset-helm-lint.yml
vendored
2
.github/workflows/superset-helm-lint.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
4
.github/workflows/superset-helm-release.yml
vendored
4
.github/workflows/superset-helm-release.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref_name }}
|
||||
persist-credentials: true
|
||||
@@ -101,7 +101,7 @@ jobs:
|
||||
CR_RELEASE_NAME_TEMPLATE: "superset-helm-chart-{{ .Version }}"
|
||||
|
||||
- name: Open Pull Request
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const branchName = '${{ env.branch_name }}';
|
||||
|
||||
23
.github/workflows/superset-playwright.yml
vendored
23
.github/workflows/superset-playwright.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Playwright E2E Tests
|
||||
name: Playwright Experimental Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -23,9 +23,10 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
playwright-tests:
|
||||
# NOTE: Required Playwright tests are in superset-e2e.yml (E2E / playwright-tests)
|
||||
# This workflow contains only experimental tests that run in shadow mode
|
||||
playwright-tests-experimental:
|
||||
runs-on: ubuntu-22.04
|
||||
# Allow workflow to succeed even if tests fail during shadow mode
|
||||
continue-on-error: true
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -59,21 +60,21 @@ jobs:
|
||||
# Conditional checkout based on context (same as Cypress workflow)
|
||||
- name: Checkout for push or pull_request event
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Checkout using ref (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
submodules: recursive
|
||||
- name: Checkout using PR ID (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
@@ -99,7 +100,7 @@ jobs:
|
||||
run: testdata
|
||||
- name: Setup Node.js
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install npm dependencies
|
||||
@@ -117,13 +118,13 @@ jobs:
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: playwright-install
|
||||
- name: Run Playwright
|
||||
- name: Run Playwright (Experimental Tests)
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=4096"
|
||||
with:
|
||||
run: playwright-run ${{ matrix.app_root }}
|
||||
run: playwright-run "${{ matrix.app_root }}" experimental/
|
||||
- name: Set safe app root
|
||||
if: failure()
|
||||
id: set-safe-app-root
|
||||
@@ -132,10 +133,10 @@ jobs:
|
||||
SAFE_APP_ROOT=${APP_ROOT//\//_}
|
||||
echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT
|
||||
- name: Upload Playwright Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
if: failure()
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/superset-frontend/playwright-results/
|
||||
${{ github.workspace }}/superset-frontend/test-results/
|
||||
name: playwright-artifact-${{ github.run_id }}-${{ github.job }}-${{ matrix.browser }}--${{ steps.set-safe-app-root.outputs.safe_app_root }}
|
||||
name: playwright-experimental-artifact-${{ github.run_id }}-${{ github.job }}-${{ matrix.browser }}--${{ steps.set-safe-app-root.outputs.safe_app_root }}
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -152,7 +152,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
6
.github/workflows/superset-translations.yml
vendored
6
.github/workflows/superset-translations.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.check.outputs.frontend
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install dependencies
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/superset-websocket.yml
vendored
2
.github/workflows/superset-websocket.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install dependencies
|
||||
|
||||
4
.github/workflows/supersetbot.yml
vendored
4
.github/workflows/supersetbot.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
steps:
|
||||
- name: Quickly add thumbs up!
|
||||
if: github.event_name == 'issue_comment' && contains(github.event.comment.body, '@supersetbot')
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/')
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
});
|
||||
|
||||
- name: "Checkout ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
8
.github/workflows/tag-release.yml
vendored
8
.github/workflows/tag-release.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
build: "true"
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
@@ -107,12 +107,12 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
|
||||
4
.github/workflows/tech-debt.yml
vendored
4
.github/workflows/tech-debt.yml
vendored
@@ -27,10 +27,10 @@ jobs:
|
||||
name: Generate Reports
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -33,6 +33,7 @@ cover
|
||||
.env
|
||||
.envrc
|
||||
.idea
|
||||
.roo
|
||||
.mypy_cache
|
||||
.python-version
|
||||
.tox
|
||||
@@ -121,6 +122,8 @@ docker/requirements-local.txt
|
||||
|
||||
cache/
|
||||
docker/*local*
|
||||
docker/superset-websocket/config.json
|
||||
docker-compose.override.yml
|
||||
|
||||
.temp_cache
|
||||
|
||||
@@ -134,3 +137,5 @@ PROJECT.md
|
||||
.aider*
|
||||
.claude_rc*
|
||||
.env.local
|
||||
oxc-custom-build/
|
||||
*.code-workspace
|
||||
|
||||
@@ -60,15 +60,29 @@ repos:
|
||||
args: ["--markdown-linebreak-ext=md"]
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: eslint-frontend
|
||||
name: eslint (frontend)
|
||||
entry: ./scripts/eslint.sh
|
||||
- id: prettier-frontend
|
||||
name: prettier (frontend)
|
||||
entry: bash -c 'cd superset-frontend && for file in "$@"; do npx prettier --write "${file#superset-frontend/}"; done'
|
||||
language: system
|
||||
pass_filenames: true
|
||||
files: ^superset-frontend/.*\.(js|jsx|ts|tsx|css|scss|sass|json)$
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: oxlint-frontend
|
||||
name: oxlint (frontend)
|
||||
entry: ./scripts/oxlint.sh
|
||||
language: system
|
||||
pass_filenames: true
|
||||
files: ^superset-frontend/.*\.(js|jsx|ts|tsx)$
|
||||
- id: custom-rules-frontend
|
||||
name: custom rules (frontend)
|
||||
entry: ./scripts/check-custom-rules.sh
|
||||
language: system
|
||||
pass_filenames: true
|
||||
files: ^superset-frontend/.*\.(js|jsx|ts|tsx)$
|
||||
- id: eslint-docs
|
||||
name: eslint (docs)
|
||||
entry: bash -c 'cd docs && FILES=$(echo "$@" | sed "s|docs/||g") && yarn eslint --fix --ext .js,.jsx,.ts,.tsx --quiet $FILES'
|
||||
entry: bash -c 'cd docs && FILES=$(echo "$@" | sed "s|docs/||g") && yarn eslint --fix --quiet $FILES'
|
||||
language: system
|
||||
pass_filenames: true
|
||||
files: ^docs/.*\.(js|jsx|ts|tsx)$
|
||||
@@ -92,12 +106,19 @@ repos:
|
||||
files: helm
|
||||
verbose: false
|
||||
args: ["--log-level", "error"]
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.9.7
|
||||
# Using local hooks ensures ruff version matches requirements/development.txt
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
name: ruff-format
|
||||
entry: ruff format
|
||||
language: system
|
||||
types: [python]
|
||||
- id: ruff
|
||||
args: [--fix]
|
||||
name: ruff
|
||||
entry: ruff check --fix --show-fixes
|
||||
language: system
|
||||
types: [python]
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: pylint
|
||||
@@ -110,9 +131,12 @@ repos:
|
||||
- -c
|
||||
- |
|
||||
TARGET_BRANCH=${GITHUB_BASE_REF:-master}
|
||||
git fetch origin "$TARGET_BRANCH"
|
||||
BASE=$(git merge-base origin/"$TARGET_BRANCH" HEAD)
|
||||
files=$(git diff --name-only --diff-filter=ACM "$BASE"..HEAD | grep '^superset/.*\.py$' || true)
|
||||
# Only fetch if we're not in CI (CI already has all refs)
|
||||
if [ -z "$CI" ]; then
|
||||
git fetch --no-recurse-submodules origin "$TARGET_BRANCH" 2>/dev/null || true
|
||||
fi
|
||||
BASE=$(git merge-base origin/"$TARGET_BRANCH" HEAD 2>/dev/null) || BASE="HEAD"
|
||||
files=$(git diff --name-only --diff-filter=ACM "$BASE"..HEAD 2>/dev/null | grep '^superset/.*\.py$' || true)
|
||||
if [ -n "$files" ]; then
|
||||
pylint --rcfile=.pylintrc --load-plugins=superset.extensions.pylint --reports=no $files
|
||||
else
|
||||
|
||||
@@ -53,7 +53,7 @@ extension-pkg-whitelist=pyarrow
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable=all
|
||||
enable=disallowed-json-import,disallowed-sql-import,consider-using-transaction
|
||||
enable=json-import,disallowed-sql-import,consider-using-transaction
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
.nvmrc
|
||||
.prettierrc
|
||||
.rat-excludes
|
||||
.swcrc
|
||||
.*log
|
||||
.*pyc
|
||||
.*lock
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
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
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
@@ -16,9 +16,23 @@
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
# Contributing to Apache Superset
|
||||
|
||||
Contributions are welcome and are greatly appreciated! Every
|
||||
little bit helps, and credit will always be given.
|
||||
|
||||
All matters related to contributions have moved to [this section of
|
||||
the official Superset documentation](https://superset.apache.org/docs/contributing/). Source for the documentation is
|
||||
[located here](https://github.com/apache/superset/tree/master/docs/docs).
|
||||
## Developer Portal
|
||||
|
||||
All developer and contribution documentation has moved to the Apache Superset Developer Portal:
|
||||
|
||||
**[📚 View the Developer Portal →](https://superset.apache.org/developer_portal/)**
|
||||
|
||||
The Developer Portal includes comprehensive guides for:
|
||||
- [Contributing Overview](https://superset.apache.org/developer_portal/contributing/overview)
|
||||
- [Development Setup](https://superset.apache.org/developer_portal/contributing/development-setup)
|
||||
- [Submitting Pull Requests](https://superset.apache.org/developer_portal/contributing/submitting-pr)
|
||||
- [Contribution Guidelines](https://superset.apache.org/developer_portal/contributing/guidelines)
|
||||
- [Code Review Process](https://superset.apache.org/developer_portal/contributing/code-review)
|
||||
- [Development How-tos](https://superset.apache.org/developer_portal/contributing/howtos)
|
||||
|
||||
Source for the Developer Portal documentation is [located here](https://github.com/apache/superset/tree/master/docs/developer_portal).
|
||||
|
||||
11
Dockerfile
11
Dockerfile
@@ -18,7 +18,7 @@
|
||||
######################################################################
|
||||
# Node stage to deal with static asset construction
|
||||
######################################################################
|
||||
ARG PY_VER=3.11.13-slim-trixie
|
||||
ARG PY_VER=3.11.14-slim-trixie
|
||||
|
||||
# If BUILDPLATFORM is null, set it to 'amd64' (or leave as is otherwise).
|
||||
ARG BUILDPLATFORM=${BUILDPLATFORM:-amd64}
|
||||
@@ -26,6 +26,9 @@ ARG BUILDPLATFORM=${BUILDPLATFORM:-amd64}
|
||||
# Include translations in the final build
|
||||
ARG BUILD_TRANSLATIONS="false"
|
||||
|
||||
# Build arg to pre-populate examples DuckDB file
|
||||
ARG LOAD_EXAMPLES_DUCKDB="false"
|
||||
|
||||
######################################################################
|
||||
# superset-node-ci used as a base for building frontend assets and CI
|
||||
######################################################################
|
||||
@@ -143,8 +146,8 @@ RUN if [ "${BUILD_TRANSLATIONS}" = "true" ]; then \
|
||||
######################################################################
|
||||
FROM python-base AS python-common
|
||||
|
||||
# Build arg to pre-populate examples DuckDB file
|
||||
ARG LOAD_EXAMPLES_DUCKDB="false"
|
||||
# Re-declare build arg to receive it in this stage
|
||||
ARG LOAD_EXAMPLES_DUCKDB
|
||||
|
||||
ENV SUPERSET_HOME="/app/superset_home" \
|
||||
HOME="/app/superset_home" \
|
||||
@@ -168,6 +171,8 @@ RUN mkdir -p \
|
||||
&& touch superset/static/version_info.json
|
||||
|
||||
# Install Playwright and optionally setup headless browsers
|
||||
ENV PLAYWRIGHT_BROWSERS_PATH=/usr/local/share/playwright-browsers
|
||||
|
||||
ARG INCLUDE_CHROMIUM="false"
|
||||
ARG INCLUDE_FIREFOX="false"
|
||||
RUN --mount=type=cache,target=${SUPERSET_HOME}/.cache/uv \
|
||||
|
||||
121
LINTING_ARCHITECTURE.md
Normal file
121
LINTING_ARCHITECTURE.md
Normal file
@@ -0,0 +1,121 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Superset Frontend Linting Architecture
|
||||
|
||||
## Overview
|
||||
We use a hybrid linting approach combining OXC (fast, standard rules) with custom AST-based checks for Superset-specific patterns.
|
||||
|
||||
## Components
|
||||
|
||||
### 1. Primary Linter: OXC
|
||||
- **What**: Oxidation Compiler's linter (oxlint)
|
||||
- **Handles**: 95% of linting rules (standard ESLint rules, TypeScript, React, etc.)
|
||||
- **Speed**: ~50-100x faster than ESLint
|
||||
- **Config**: `oxlint.json`
|
||||
|
||||
### 2. Custom Rule Checker
|
||||
- **What**: Node.js AST-based script
|
||||
- **Handles**: Superset-specific rules:
|
||||
- No literal colors (use theme)
|
||||
- No FontAwesome icons (use Icons component)
|
||||
- No template vars in i18n
|
||||
- **Speed**: Fast enough for pre-commit
|
||||
- **Script**: `scripts/check-custom-rules.js`
|
||||
|
||||
## Developer Workflow
|
||||
|
||||
### Local Development
|
||||
```bash
|
||||
# Fast linting (OXC only)
|
||||
npm run lint
|
||||
|
||||
# Full linting (OXC + custom rules)
|
||||
npm run lint:full
|
||||
|
||||
# Auto-fix what's possible
|
||||
npm run lint-fix
|
||||
```
|
||||
|
||||
### Pre-commit
|
||||
1. OXC runs first (via `scripts/oxlint.sh`)
|
||||
2. Custom rules check runs second (lightweight, AST-based)
|
||||
3. Both must pass for commit to succeed
|
||||
|
||||
### CI Pipeline
|
||||
```yaml
|
||||
- name: Lint with OXC
|
||||
run: npm run lint
|
||||
|
||||
- name: Check custom rules
|
||||
run: npm run check:custom-rules
|
||||
```
|
||||
|
||||
## Why This Architecture?
|
||||
|
||||
### ✅ Pros
|
||||
1. **No binary distribution issues** - ASF compatible
|
||||
2. **Fast performance** - OXC for bulk, lightweight script for custom
|
||||
3. **Maintainable** - Custom rules in JavaScript, not Rust
|
||||
4. **Flexible** - Can evolve as OXC adds plugin support
|
||||
5. **Cacheable** - Both OXC and Node.js are standard tools
|
||||
|
||||
### ❌ Cons
|
||||
1. **Two tools** - Slightly more complex than single linter
|
||||
2. **Duplicate parsing** - Files parsed twice (once by each tool)
|
||||
|
||||
### 🔄 Migration Path
|
||||
When OXC supports JavaScript plugins:
|
||||
1. Convert `check-custom-rules.js` to OXC plugin format
|
||||
2. Consolidate back to single tool
|
||||
3. Keep same rules and developer experience
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [x] OXC for standard linting
|
||||
- [x] Pre-commit integration
|
||||
- [ ] Custom rules script
|
||||
- [ ] Combine in npm scripts
|
||||
- [ ] Update CI pipeline
|
||||
- [ ] Developer documentation
|
||||
|
||||
## Performance Targets
|
||||
|
||||
| Operation | Target Time | Current |
|
||||
|-----------|------------|---------|
|
||||
| Pre-commit (changed files) | <2s | ✅ 1.5s |
|
||||
| Full lint (all files) | <10s | ✅ 8s |
|
||||
| Custom rules check | <5s | 🔄 TBD |
|
||||
|
||||
## Caching Strategy
|
||||
|
||||
### Local Development
|
||||
- OXC: Built-in incremental checking
|
||||
- Custom rules: Use file hash cache (similar to pytest cache)
|
||||
|
||||
### CI
|
||||
- Cache `node_modules` (includes oxlint binary)
|
||||
- Cache custom rules results by commit hash
|
||||
- Skip unchanged files using git diff
|
||||
|
||||
## Future Improvements
|
||||
|
||||
1. **When OXC adds plugin support**: Migrate custom rules to OXC plugins
|
||||
2. **Consider Biome**: Another Rust-based linter with plugin support
|
||||
3. **AST sharing**: Investigate sharing AST between tools to avoid double parsing
|
||||
11
README.md
11
README.md
@@ -17,17 +17,6 @@ specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
# Superset
|
||||
|
||||
[](https://opensource.org/license/apache-2-0)
|
||||
[](https://github.com/apache/superset/releases/latest)
|
||||
[](https://github.com/apache/superset/actions)
|
||||
[](https://badge.fury.io/py/apache_superset)
|
||||
[](https://codecov.io/github/apache/superset)
|
||||
[](https://pypi.python.org/pypi/apache_superset)
|
||||
[](http://bit.ly/join-superset-slack)
|
||||
[](https://superset.apache.org)
|
||||
|
||||
<picture width="500">
|
||||
<source
|
||||
width="600"
|
||||
|
||||
@@ -68,7 +68,7 @@ These features flags are **safe for production**. They have been tested and will
|
||||
|
||||
### Flags retained for runtime configuration
|
||||
|
||||
Currently some of our feature flags act as dynamic configurations that can changed
|
||||
Currently some of our feature flags act as dynamic configurations that can change
|
||||
on the fly. This acts in contradiction with the typical ephemeral feature flag use case,
|
||||
where the flag is used to mature a feature, and eventually deprecated once the feature is
|
||||
solid. Eventually we'll likely refactor these under a more formal "dynamic configurations" managed
|
||||
|
||||
129
UPDATING.md
129
UPDATING.md
@@ -23,6 +23,108 @@ This file documents any backwards-incompatible changes in Superset and
|
||||
assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
|
||||
### MCP Service
|
||||
|
||||
The MCP (Model Context Protocol) service enables AI assistants and automation tools to interact programmatically with Superset.
|
||||
|
||||
#### New Features
|
||||
- MCP service infrastructure with FastMCP framework
|
||||
- Tools for dashboards, charts, datasets, SQL Lab, and instance metadata
|
||||
- Optional dependency: install with `pip install apache-superset[fastmcp]`
|
||||
- Runs as separate process from Superset web server
|
||||
- JWT-based authentication for production deployments
|
||||
|
||||
#### New Configuration Options
|
||||
|
||||
**Development** (single-user, local testing):
|
||||
```python
|
||||
# superset_config.py
|
||||
MCP_DEV_USERNAME = "admin" # User for MCP authentication
|
||||
MCP_SERVICE_HOST = "localhost"
|
||||
MCP_SERVICE_PORT = 5008
|
||||
```
|
||||
|
||||
**Production** (JWT-based, multi-user):
|
||||
```python
|
||||
# superset_config.py
|
||||
MCP_AUTH_ENABLED = True
|
||||
MCP_JWT_ISSUER = "https://your-auth-provider.com"
|
||||
MCP_JWT_AUDIENCE = "superset-mcp"
|
||||
MCP_JWT_ALGORITHM = "RS256" # or "HS256" for shared secrets
|
||||
|
||||
# Option 1: Use JWKS endpoint (recommended for RS256)
|
||||
MCP_JWKS_URI = "https://auth.example.com/.well-known/jwks.json"
|
||||
|
||||
# Option 2: Use static public key (RS256)
|
||||
MCP_JWT_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----..."
|
||||
|
||||
# Option 3: Use shared secret (HS256)
|
||||
MCP_JWT_ALGORITHM = "HS256"
|
||||
MCP_JWT_SECRET = "your-shared-secret-key"
|
||||
|
||||
# Optional overrides
|
||||
MCP_SERVICE_HOST = "0.0.0.0"
|
||||
MCP_SERVICE_PORT = 5008
|
||||
MCP_SESSION_CONFIG = {
|
||||
"SESSION_COOKIE_SECURE": True,
|
||||
"SESSION_COOKIE_HTTPONLY": True,
|
||||
"SESSION_COOKIE_SAMESITE": "Strict",
|
||||
}
|
||||
```
|
||||
|
||||
#### Running the MCP Service
|
||||
|
||||
```bash
|
||||
# Development
|
||||
superset mcp run --port 5008 --debug
|
||||
|
||||
# Production
|
||||
superset mcp run --port 5008
|
||||
|
||||
# With factory config
|
||||
superset mcp run --port 5008 --use-factory-config
|
||||
```
|
||||
|
||||
#### Deployment Considerations
|
||||
|
||||
The MCP service runs as a **separate process** from the Superset web server.
|
||||
|
||||
**Important**:
|
||||
- Requires same Python environment and configuration as Superset
|
||||
- Shares database connections with main Superset app
|
||||
- Can be scaled independently from web server
|
||||
- Requires `fastmcp` package (optional dependency)
|
||||
|
||||
**Installation**:
|
||||
```bash
|
||||
# Install with MCP support
|
||||
pip install apache-superset[fastmcp]
|
||||
|
||||
# Or add to requirements.txt
|
||||
apache-superset[fastmcp]>=X.Y.Z
|
||||
```
|
||||
|
||||
**Process Management**:
|
||||
Use systemd, supervisord, or Kubernetes to manage the MCP service process.
|
||||
See `superset/mcp_service/PRODUCTION.md` for deployment guides.
|
||||
|
||||
**Security**:
|
||||
- Development: Uses `MCP_DEV_USERNAME` for single-user access
|
||||
- Production: **MUST** configure JWT authentication
|
||||
- See `superset/mcp_service/SECURITY.md` for details
|
||||
|
||||
#### Documentation
|
||||
|
||||
- Architecture: `superset/mcp_service/ARCHITECTURE.md`
|
||||
- Security: `superset/mcp_service/SECURITY.md`
|
||||
- Production: `superset/mcp_service/PRODUCTION.md`
|
||||
- Developer Guide: `superset/mcp_service/CLAUDE.md`
|
||||
- Quick Start: `superset/mcp_service/README.md`
|
||||
|
||||
---
|
||||
|
||||
- [35621](https://github.com/apache/superset/pull/35621): The default hash algorithm has changed from MD5 to SHA-256 for improved security and FedRAMP compliance. This affects cache keys for thumbnails, dashboard digests, chart digests, and filter option names. Existing cached data will be invalidated upon upgrade. To opt out of this change and maintain backward compatibility, set `HASH_ALGORITHM = "md5"` in your `superset_config.py`.
|
||||
- [33055](https://github.com/apache/superset/pull/33055): Upgrades Flask-AppBuilder to 5.0.0. The AUTH_OID authentication type has been deprecated and is no longer available as an option in Flask-AppBuilder. OpenID (OID) is considered a deprecated authentication protocol - if you are using AUTH_OID, you will need to migrate to an alternative authentication method such as OAuth, LDAP, or database authentication before upgrading.
|
||||
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
|
||||
- [34871](https://github.com/apache/superset/pull/34871): Fixed Jest test hanging issue from Ant Design v5 upgrade. MessageChannel is now mocked in test environment to prevent rc-overflow from causing Jest to hang. Test environment only - no production impact.
|
||||
@@ -41,10 +143,35 @@ Note: Pillow is now a required dependency (previously optional) to support image
|
||||
- [33116](https://github.com/apache/superset/pull/33116) In Echarts Series charts (e.g. Line, Area, Bar, etc.) charts, the `x_axis_sort_series` and `x_axis_sort_series_ascending` form data items have been renamed with `x_axis_sort` and `x_axis_sort_asc`.
|
||||
There's a migration added that can potentially affect a significant number of existing charts.
|
||||
- [32317](https://github.com/apache/superset/pull/32317) The horizontal filter bar feature is now out of testing/beta development and its feature flag `HORIZONTAL_FILTER_BAR` has been removed.
|
||||
- [31590](https://github.com/apache/superset/pull/31590) Marks the begining of intricate work around supporting dynamic Theming, and breaks support for [THEME_OVERRIDES](https://github.com/apache/superset/blob/732de4ac7fae88e29b7f123b6cbb2d7cd411b0e4/superset/config.py#L671) in favor of a new theming system based on AntD V5. Likely this will be in disrepair until settling over the 5.x lifecycle.
|
||||
- [31590](https://github.com/apache/superset/pull/31590) Marks the beginning of intricate work around supporting dynamic Theming, and breaks support for [THEME_OVERRIDES](https://github.com/apache/superset/blob/732de4ac7fae88e29b7f123b6cbb2d7cd411b0e4/superset/config.py#L671) in favor of a new theming system based on AntD V5. Likely this will be in disrepair until settling over the 5.x lifecycle.
|
||||
- [32432](https://github.com/apache/superset/pull/31260) Moves the List Roles FAB view to the frontend and requires `FAB_ADD_SECURITY_API` to be enabled in the configuration and `superset init` to be executed.
|
||||
- [34319](https://github.com/apache/superset/pull/34319) Drill to Detail and Drill By is now supported in Embedded mode, and also with the `DASHBOARD_RBAC` FF. If you don't want to expose these features in Embedded / `DASHBOARD_RBAC`, make sure the roles used for Embedded / `DASHBOARD_RBAC`don't have the required permissions to perform D2D actions.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
#### CUSTOM_FONT_URLS removed
|
||||
|
||||
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 (5.x):**
|
||||
```python
|
||||
CUSTOM_FONT_URLS = [
|
||||
"https://fonts.example.com/myfont.css",
|
||||
]
|
||||
```
|
||||
|
||||
**After (6.0):**
|
||||
```python
|
||||
THEME_DEFAULT = {
|
||||
"token": {
|
||||
"fontUrls": [
|
||||
"https://fonts.example.com/myfont.css",
|
||||
],
|
||||
# ... other tokens
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5.0.0
|
||||
|
||||
- [31976](https://github.com/apache/superset/pull/31976) Removed the `DISABLE_LEGACY_DATASOURCE_EDITOR` feature flag. The previous value of the feature flag was `True` and now the feature is permanently removed.
|
||||
|
||||
@@ -137,9 +137,9 @@ services:
|
||||
- /home/superset-websocket/node_modules
|
||||
- /home/superset-websocket/dist
|
||||
|
||||
# Mounting a config file that contains a dummy secret required to boot up.
|
||||
# do not use this docker compose in production
|
||||
- ./docker/superset-websocket/config.json:/home/superset-websocket/config.json
|
||||
# Mount config file. Create your own docker/superset-websocket/config.json
|
||||
# for custom settings, then point to it here. Do not use this example in production.
|
||||
- ./docker/superset-websocket/config.example.json:/home/superset-websocket/config.json:ro
|
||||
environment:
|
||||
- PORT=8080
|
||||
- REDIS_HOST=redis
|
||||
|
||||
@@ -34,8 +34,24 @@ intended for use with local development.
|
||||
|
||||
### Local overrides
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
To override environment variables locally, create a `./docker/.env-local` file (git-ignored). This file will be loaded after `.env` and can override any settings.
|
||||
|
||||
#### Python Configuration
|
||||
|
||||
In order to override configuration settings locally, simply make a copy of [`./docker/pythonpath_dev/superset_config_local.example`](./pythonpath_dev/superset_config_local.example)
|
||||
into `./docker/pythonpath_dev/superset_config_docker.py` (git ignored) and fill in your overrides.
|
||||
into `./docker/pythonpath_dev/superset_config_docker.py` (git-ignored) and fill in your overrides.
|
||||
|
||||
#### WebSocket Configuration
|
||||
|
||||
To customize the WebSocket server configuration, create `./docker/superset-websocket/config.json` (git-ignored) based on [`./docker/superset-websocket/config.example.json`](./superset-websocket/config.example.json).
|
||||
|
||||
Then update the `superset-websocket`.`volumes` config to mount it.
|
||||
|
||||
#### Docker Compose Overrides
|
||||
|
||||
For advanced Docker Compose customization, create a `docker-compose-override.yml` file (git-ignored) to override or extend services without modifying the main compose file.
|
||||
|
||||
### Local packages
|
||||
|
||||
|
||||
@@ -80,12 +80,16 @@ case "${1}" in
|
||||
;;
|
||||
app)
|
||||
echo "Starting web app (using development server)..."
|
||||
flask run -p $PORT --reload --debugger --without-threads --host=0.0.0.0
|
||||
flask run -p $PORT --reload --debugger --without-threads --host=0.0.0.0 --exclude-patterns "*/node_modules/*:*/.venv/*:*/build/*:*/__pycache__/*"
|
||||
;;
|
||||
app-gunicorn)
|
||||
echo "Starting web app..."
|
||||
/usr/bin/run-server.sh
|
||||
;;
|
||||
mcp)
|
||||
echo "Starting MCP service..."
|
||||
superset mcp run --host 0.0.0.0 --port ${MCP_PORT:-5008} --debug
|
||||
;;
|
||||
*)
|
||||
echo "Unknown Operation!!!"
|
||||
;;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
# Import all settings from the main config first
|
||||
from flask_caching.backends.filesystemcache import FileSystemCache
|
||||
|
||||
from superset_config import * # noqa: F403
|
||||
|
||||
# Override caching to use simple in-memory cache instead of Redis
|
||||
|
||||
22
docker/superset-websocket/config.example.json
Normal file
22
docker/superset-websocket/config.example.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
642
docs/DOCS_CLAUDE.md
Normal file
642
docs/DOCS_CLAUDE.md
Normal file
@@ -0,0 +1,642 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# LLM Context Guide for Apache Superset Documentation
|
||||
|
||||
This guide helps LLMs work with the Apache Superset documentation site built with Docusaurus 3.
|
||||
|
||||
## 📍 Current Directory Context
|
||||
|
||||
You are currently in the `/docs` subdirectory of the Apache Superset repository. When referencing files from the main codebase, use `../` to access the parent directory.
|
||||
|
||||
```
|
||||
/Users/evan_1/GitHub/superset/ # Main repository root
|
||||
├── superset/ # Python backend code
|
||||
├── superset-frontend/ # React/TypeScript frontend
|
||||
└── docs/ # Documentation site (YOU ARE HERE)
|
||||
├── docs/ # Main documentation content
|
||||
├── developer_portal/ # Developer guides (currently disabled)
|
||||
├── components/ # Component playground (currently disabled)
|
||||
└── docusaurus.config.ts # Site configuration
|
||||
```
|
||||
|
||||
## 🚀 Quick Commands
|
||||
|
||||
```bash
|
||||
# Development
|
||||
yarn start # Start dev server on http://localhost:3000
|
||||
yarn stop # Stop running dev server
|
||||
yarn build # Build production site
|
||||
yarn serve # Serve built site locally
|
||||
|
||||
# Version Management (USE THESE, NOT docusaurus commands)
|
||||
yarn version:add:docs <version> # Add new docs version
|
||||
yarn version:add:developer_portal <version> # Add developer portal version
|
||||
yarn version:add:components <version> # Add components version
|
||||
yarn version:remove:docs <version> # Remove docs version
|
||||
yarn version:remove:developer_portal <version> # Remove developer portal version
|
||||
yarn version:remove:components <version> # Remove components version
|
||||
|
||||
# Quality Checks
|
||||
yarn typecheck # TypeScript validation
|
||||
yarn eslint # Lint TypeScript/JavaScript files
|
||||
```
|
||||
|
||||
## 📁 Documentation Structure
|
||||
|
||||
### Main Documentation (`/docs`)
|
||||
The primary documentation lives in `/docs` with this structure:
|
||||
|
||||
```
|
||||
docs/
|
||||
├── intro.md # Auto-generated from ../README.md
|
||||
├── quickstart.mdx # Getting started guide
|
||||
├── api.mdx # API reference with Swagger UI
|
||||
├── faq.mdx # Frequently asked questions
|
||||
├── installation/ # Installation guides
|
||||
│ ├── installation-methods.mdx
|
||||
│ ├── docker-compose.mdx
|
||||
│ ├── docker-builds.mdx
|
||||
│ ├── kubernetes.mdx
|
||||
│ ├── pypi.mdx
|
||||
│ └── architecture.mdx
|
||||
├── configuration/ # Configuration guides
|
||||
│ ├── configuring-superset.mdx
|
||||
│ ├── alerts-reports.mdx
|
||||
│ ├── caching.mdx
|
||||
│ ├── databases.mdx
|
||||
│ └── [more config docs]
|
||||
├── using-superset/ # User guides
|
||||
│ ├── creating-your-first-dashboard.md
|
||||
│ ├── exploring-data.mdx
|
||||
│ └── [more user docs]
|
||||
├── contributing/ # Contributor guides
|
||||
│ ├── development.mdx
|
||||
│ ├── testing-locally.mdx
|
||||
│ └── [more contributor docs]
|
||||
└── security/ # Security documentation
|
||||
├── security.mdx
|
||||
└── [security guides]
|
||||
```
|
||||
|
||||
### Developer Portal (`/developer_portal`) - Currently Disabled
|
||||
When enabled, contains developer-focused content:
|
||||
- API documentation
|
||||
- Architecture guides
|
||||
- CLI tools
|
||||
- Code examples
|
||||
|
||||
### Component Playground (`/components`) - Currently Disabled
|
||||
When enabled, provides interactive component examples for UI development.
|
||||
|
||||
## 📝 Documentation Standards
|
||||
|
||||
### File Types
|
||||
- **`.md` files**: Basic Markdown documents
|
||||
- **`.mdx` files**: Markdown with JSX - can include React components
|
||||
- **`.tsx` files in `/src`**: Custom React components and pages
|
||||
|
||||
### Frontmatter Structure
|
||||
Every documentation page should have frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: Page Title
|
||||
description: Brief description for SEO
|
||||
sidebar_position: 1 # Optional: controls order in sidebar
|
||||
---
|
||||
```
|
||||
|
||||
### MDX Component Usage
|
||||
MDX files can import and use React components:
|
||||
|
||||
```mdx
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="npm" default>
|
||||
```bash
|
||||
npm install superset
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="yarn">
|
||||
```bash
|
||||
yarn add superset
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
### Code Blocks
|
||||
Use triple backticks with language identifiers:
|
||||
|
||||
````markdown
|
||||
```python
|
||||
def hello_world():
|
||||
print("Hello, Superset!")
|
||||
```
|
||||
|
||||
```sql title="Example Query"
|
||||
SELECT * FROM users WHERE active = true;
|
||||
```
|
||||
|
||||
```bash
|
||||
# Installation command
|
||||
pip install apache-superset
|
||||
```
|
||||
````
|
||||
|
||||
### Admonitions
|
||||
Docusaurus supports various admonition types:
|
||||
|
||||
```markdown
|
||||
:::note
|
||||
This is a note
|
||||
:::
|
||||
|
||||
:::tip
|
||||
This is a tip
|
||||
:::
|
||||
|
||||
:::warning
|
||||
This is a warning
|
||||
:::
|
||||
|
||||
:::danger
|
||||
This is a danger warning
|
||||
:::
|
||||
|
||||
:::info
|
||||
This is an info box
|
||||
:::
|
||||
```
|
||||
|
||||
## 🔄 Version Management
|
||||
|
||||
### Version Configuration
|
||||
Versions are managed through `versions-config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"docs": {
|
||||
"disabled": false,
|
||||
"lastVersion": "6.0.0", // Default version shown
|
||||
"includeCurrentVersion": true, // Show "Next" version
|
||||
"onlyIncludeVersions": ["current", "6.0.0"],
|
||||
"versions": {
|
||||
"current": {
|
||||
"label": "Next",
|
||||
"path": "",
|
||||
"banner": "unreleased" // Shows warning banner
|
||||
},
|
||||
"6.0.0": {
|
||||
"label": "6.0.0",
|
||||
"path": "6.0.0",
|
||||
"banner": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Creating New Versions
|
||||
**IMPORTANT**: Always use the custom scripts, NOT native Docusaurus commands:
|
||||
|
||||
```bash
|
||||
# ✅ CORRECT - Updates both Docusaurus and versions-config.json
|
||||
yarn version:add:docs 6.1.0
|
||||
|
||||
# ❌ WRONG - Only updates Docusaurus, breaks version dropdown
|
||||
yarn docusaurus docs:version 6.1.0
|
||||
```
|
||||
|
||||
### Version Files Created
|
||||
When versioning, these files are created:
|
||||
- `versioned_docs/version-X.X.X/` - Snapshot of current docs
|
||||
- `versioned_sidebars/version-X.X.X-sidebars.json` - Sidebar config
|
||||
- `versions.json` - List of all versions
|
||||
|
||||
## 🎨 Styling and Theming
|
||||
|
||||
### Custom CSS
|
||||
Add custom styles in `/src/css/custom.css`:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--ifm-color-primary: #20a7c9;
|
||||
--ifm-code-font-size: 95%;
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Components
|
||||
Create React components in `/src/components/`:
|
||||
|
||||
```tsx
|
||||
// src/components/FeatureCard.tsx
|
||||
import React from 'react';
|
||||
|
||||
export default function FeatureCard({title, description}) {
|
||||
return (
|
||||
<div className="card">
|
||||
<h3>{title}</h3>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Use in MDX:
|
||||
|
||||
```mdx
|
||||
import FeatureCard from '@site/src/components/FeatureCard';
|
||||
|
||||
<FeatureCard
|
||||
title="Fast"
|
||||
description="Lightning fast queries"
|
||||
/>
|
||||
```
|
||||
|
||||
## 📦 Key Dependencies
|
||||
|
||||
- **Docusaurus 3.8.1**: Static site generator
|
||||
- **React 18.3**: UI framework
|
||||
- **Ant Design 5.26**: Component library
|
||||
- **@superset-ui/core**: Superset UI components
|
||||
- **Swagger UI React**: API documentation
|
||||
- **Prism**: Syntax highlighting
|
||||
|
||||
## 🔗 Linking Strategies
|
||||
|
||||
### Internal Links
|
||||
Use relative paths for internal documentation:
|
||||
|
||||
```markdown
|
||||
[Installation Guide](./installation/docker-compose)
|
||||
[Configuration](../configuration/configuring-superset)
|
||||
```
|
||||
|
||||
### External Links
|
||||
Always use full URLs:
|
||||
|
||||
```markdown
|
||||
[Apache Superset GitHub](https://github.com/apache/superset)
|
||||
```
|
||||
|
||||
### Linking to Code
|
||||
Reference code in the main repository:
|
||||
|
||||
```markdown
|
||||
See the [main configuration file](https://github.com/apache/superset/blob/master/superset/config.py)
|
||||
```
|
||||
|
||||
## 🛠️ Common Documentation Tasks
|
||||
|
||||
### Adding a New Guide
|
||||
1. Create the `.mdx` file in the appropriate directory
|
||||
2. Add frontmatter with title and description
|
||||
3. Update sidebar if needed (for manual sidebar configs)
|
||||
|
||||
### Adding API Documentation
|
||||
The API docs use Swagger UI embedded in `/docs/api.mdx`:
|
||||
|
||||
```mdx
|
||||
import SwaggerUI from "swagger-ui-react";
|
||||
import "swagger-ui-react/swagger-ui.css";
|
||||
|
||||
<SwaggerUI url="/api/v1/openapi.json" />
|
||||
```
|
||||
|
||||
### Adding Interactive Examples
|
||||
Use MDX to create interactive documentation:
|
||||
|
||||
```mdx
|
||||
import CodeBlock from '@theme/CodeBlock';
|
||||
import MyComponentExample from '!!raw-loader!../examples/MyComponent.tsx';
|
||||
|
||||
<CodeBlock language="tsx">{MyComponentExample}</CodeBlock>
|
||||
```
|
||||
|
||||
## 📋 Documentation Checklist
|
||||
|
||||
When creating or updating documentation:
|
||||
|
||||
- [ ] Clear, descriptive title in frontmatter
|
||||
- [ ] Description for SEO in frontmatter
|
||||
- [ ] Proper heading hierarchy (h1 -> h2 -> h3)
|
||||
- [ ] Code examples with language identifiers
|
||||
- [ ] Links verified (internal and external)
|
||||
- [ ] Images have alt text
|
||||
- [ ] Admonitions used for important notes
|
||||
- [ ] Tested locally with `yarn start`
|
||||
- [ ] No broken links (check with `yarn build`)
|
||||
|
||||
## 🔍 Searching and Navigation
|
||||
|
||||
### Sidebar Configuration
|
||||
Sidebars are configured in `/sidebars.js`:
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
CustomSidebar: [
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Introduction',
|
||||
id: 'intro',
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Installation',
|
||||
items: [
|
||||
{
|
||||
type: 'autogenerated',
|
||||
dirName: 'installation',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
### Search
|
||||
Docusaurus includes Algolia DocSearch integration configured in `docusaurus.config.ts`.
|
||||
|
||||
## 🚫 Common Pitfalls to Avoid
|
||||
|
||||
1. **Never use `yarn docusaurus docs:version`** - Use `yarn version:add:docs` instead
|
||||
2. **Don't edit versioned docs directly** - Edit current docs and create new version
|
||||
3. **Avoid absolute paths in links** - Use relative paths for maintainability
|
||||
4. **Don't forget frontmatter** - Every doc needs title and description
|
||||
5. **Test builds locally** - Run `yarn build` before committing
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Dev Server Issues
|
||||
```bash
|
||||
yarn stop # Kill any running servers
|
||||
yarn clear # Clear cache
|
||||
yarn start # Restart
|
||||
```
|
||||
|
||||
### Build Failures
|
||||
```bash
|
||||
# Check for broken links
|
||||
yarn build
|
||||
|
||||
# TypeScript issues
|
||||
yarn typecheck
|
||||
|
||||
# Linting issues
|
||||
yarn eslint
|
||||
```
|
||||
|
||||
### Version Issues
|
||||
If versions don't appear in dropdown:
|
||||
1. Check `versions-config.json` includes the version
|
||||
2. Verify version files exist in `versioned_docs/`
|
||||
3. Restart dev server
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
- [Docusaurus Documentation](https://docusaurus.io/docs)
|
||||
- [MDX Documentation](https://mdxjs.com/)
|
||||
- [Superset Contributing Guide](../CONTRIBUTING.md)
|
||||
- [Main Superset Documentation](https://superset.apache.org/docs/intro)
|
||||
|
||||
## 📖 Real Examples and Patterns
|
||||
|
||||
### Example: Configuration Documentation Pattern
|
||||
From `docs/configuration/configuring-superset.mdx`:
|
||||
|
||||
```mdx
|
||||
---
|
||||
title: Configuring Superset
|
||||
hide_title: true
|
||||
sidebar_position: 1
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Configuring Superset
|
||||
|
||||
## superset_config.py
|
||||
|
||||
Superset exposes hundreds of configurable parameters through its
|
||||
[config.py module](https://github.com/apache/superset/blob/master/superset/config.py).
|
||||
|
||||
```bash
|
||||
export SUPERSET_CONFIG_PATH=/app/superset_config.py
|
||||
```
|
||||
```
|
||||
|
||||
**Key patterns:**
|
||||
- Links to source code for reference
|
||||
- Code blocks with bash/python examples
|
||||
- Environment variable documentation
|
||||
- Step-by-step configuration instructions
|
||||
|
||||
### Example: Tutorial Documentation Pattern
|
||||
From `docs/using-superset/creating-your-first-dashboard.mdx`:
|
||||
|
||||
```mdx
|
||||
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||
|
||||
## Creating Your First Dashboard
|
||||
|
||||
:::tip
|
||||
In addition to this site, [Preset.io](http://preset.io/) maintains an updated set of end-user
|
||||
documentation at [docs.preset.io](https://docs.preset.io/).
|
||||
:::
|
||||
|
||||
### Connecting to a new database
|
||||
|
||||
<img src={useBaseUrl("/img/tutorial/tutorial_01_add_database_connection.png")} width="600" />
|
||||
```
|
||||
|
||||
**Key patterns:**
|
||||
- Import Docusaurus hooks for dynamic URLs
|
||||
- Use of admonitions (:::tip) for helpful information
|
||||
- Screenshots with useBaseUrl for proper path resolution
|
||||
- Clear section hierarchy with ### subheadings
|
||||
- Step-by-step visual guides
|
||||
|
||||
### Example: API Documentation Pattern
|
||||
From `docs/api.mdx`:
|
||||
|
||||
```mdx
|
||||
import SwaggerUI from "swagger-ui-react";
|
||||
import "swagger-ui-react/swagger-ui.css";
|
||||
|
||||
## API Documentation
|
||||
|
||||
<SwaggerUI url="/api/v1/openapi.json" />
|
||||
```
|
||||
|
||||
**Key patterns:**
|
||||
- Embedding interactive Swagger UI
|
||||
- Importing necessary CSS
|
||||
- Direct API spec integration
|
||||
|
||||
### Common Image Patterns
|
||||
|
||||
```mdx
|
||||
// For images in static folder
|
||||
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||
|
||||
<img src={useBaseUrl("/img/feature-screenshot.png")} width="600" />
|
||||
|
||||
// With caption
|
||||
<figure>
|
||||
<img src={useBaseUrl("/img/dashboard.png")} alt="Dashboard view" />
|
||||
<figcaption>Superset Dashboard Interface</figcaption>
|
||||
</figure>
|
||||
```
|
||||
|
||||
### Multi-Tab Code Examples
|
||||
|
||||
```mdx
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
<Tabs
|
||||
defaultValue="docker"
|
||||
values={[
|
||||
{label: 'Docker', value: 'docker'},
|
||||
{label: 'Kubernetes', value: 'k8s'},
|
||||
{label: 'PyPI', value: 'pypi'},
|
||||
]}>
|
||||
<TabItem value="docker">
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="k8s">
|
||||
```bash
|
||||
kubectl apply -f superset.yaml
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="pypi">
|
||||
```bash
|
||||
pip install apache-superset
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
### Configuration File Examples
|
||||
|
||||
```mdx
|
||||
```python title="superset_config.py"
|
||||
# Database connection example
|
||||
SQLALCHEMY_DATABASE_URI = 'postgresql://user:password@localhost/superset'
|
||||
|
||||
# Security configuration
|
||||
SECRET_KEY = 'YOUR_SECRET_KEY_HERE'
|
||||
WTF_CSRF_ENABLED = True
|
||||
|
||||
# Feature flags
|
||||
FEATURE_FLAGS = {
|
||||
'ENABLE_TEMPLATE_PROCESSING': True,
|
||||
'DASHBOARD_NATIVE_FILTERS': True,
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### Cross-Referencing Pattern
|
||||
|
||||
```mdx
|
||||
For detailed configuration options, see:
|
||||
- [Configuring Superset](./configuration/configuring-superset)
|
||||
- [Database Connections](./configuration/databases)
|
||||
- [Security Settings](./security/security)
|
||||
|
||||
External resources:
|
||||
- [SQLAlchemy Documentation](https://docs.sqlalchemy.org/)
|
||||
- [Flask Configuration](https://flask.palletsprojects.com/config/)
|
||||
```
|
||||
|
||||
### Writing Installation Guides
|
||||
|
||||
```mdx
|
||||
## Prerequisites
|
||||
|
||||
:::warning
|
||||
Ensure you have Python 3.9+ and Node.js 16+ installed before proceeding.
|
||||
:::
|
||||
|
||||
## Installation Steps
|
||||
|
||||
1. **Clone the repository**
|
||||
```bash
|
||||
git clone https://github.com/apache/superset.git
|
||||
cd superset
|
||||
```
|
||||
|
||||
2. **Install Python dependencies**
|
||||
```bash
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
3. **Initialize the database**
|
||||
```bash
|
||||
superset db upgrade
|
||||
superset init
|
||||
```
|
||||
|
||||
:::tip Success Check
|
||||
Navigate to http://localhost:8088 and login with admin/admin
|
||||
:::
|
||||
```
|
||||
|
||||
### Documenting API Endpoints
|
||||
|
||||
```mdx
|
||||
## Chart API
|
||||
|
||||
### GET /api/v1/chart/
|
||||
|
||||
Returns a list of charts.
|
||||
|
||||
**Parameters:**
|
||||
- `page` (optional): Page number
|
||||
- `page_size` (optional): Number of items per page
|
||||
|
||||
**Example Request:**
|
||||
```bash
|
||||
curl -X GET "http://localhost:8088/api/v1/chart/" \
|
||||
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
|
||||
```
|
||||
|
||||
**Example Response:**
|
||||
```json
|
||||
{
|
||||
"count": 42,
|
||||
"result": [
|
||||
{
|
||||
"id": 1,
|
||||
"slice_name": "Sales Dashboard",
|
||||
"viz_type": "line"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Note**: This documentation site serves as the primary resource for Superset users, administrators, and contributors. Always prioritize clarity, accuracy, and completeness when creating or updating documentation.
|
||||
@@ -1,477 +0,0 @@
|
||||
---
|
||||
title: Frontend API Reference
|
||||
sidebar_position: 1
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Frontend API Reference
|
||||
|
||||
The `@apache-superset/core` package provides comprehensive APIs for frontend extension development. All APIs are organized into logical namespaces for easy discovery and use.
|
||||
|
||||
## Core API
|
||||
|
||||
The core namespace provides fundamental extension functionality.
|
||||
|
||||
### registerView
|
||||
|
||||
Registers a new view or panel in the specified contribution point.
|
||||
|
||||
```typescript
|
||||
core.registerView(
|
||||
id: string,
|
||||
component: React.ComponentType
|
||||
): Disposable
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
const panel = context.core.registerView('my-extension.panel', () => (
|
||||
<MyPanelComponent />
|
||||
));
|
||||
```
|
||||
|
||||
### getActiveView
|
||||
|
||||
Gets the currently active view in a contribution area.
|
||||
|
||||
```typescript
|
||||
core.getActiveView(area: string): View | undefined
|
||||
```
|
||||
|
||||
## Commands API
|
||||
|
||||
Manages command registration and execution.
|
||||
|
||||
### registerCommand
|
||||
|
||||
Registers a new command that can be triggered by menus, shortcuts, or programmatically.
|
||||
|
||||
```typescript
|
||||
commands.registerCommand(
|
||||
id: string,
|
||||
handler: CommandHandler
|
||||
): Disposable
|
||||
|
||||
interface CommandHandler {
|
||||
title: string;
|
||||
icon?: string;
|
||||
execute: (...args: any[]) => any;
|
||||
isEnabled?: (...args: any[]) => boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
const cmd = context.commands.registerCommand('my-extension.analyze', {
|
||||
title: 'Analyze Query',
|
||||
icon: 'BarChartOutlined',
|
||||
execute: () => {
|
||||
const query = context.sqlLab.getCurrentQuery();
|
||||
// Perform analysis
|
||||
},
|
||||
isEnabled: () => {
|
||||
return context.sqlLab.hasActiveEditor();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### executeCommand
|
||||
|
||||
Executes a registered command by ID.
|
||||
|
||||
```typescript
|
||||
commands.executeCommand(id: string, ...args: any[]): Promise<any>
|
||||
```
|
||||
|
||||
## SQL Lab API
|
||||
|
||||
Provides access to SQL Lab functionality and events.
|
||||
|
||||
### Query Access
|
||||
|
||||
```typescript
|
||||
// Get current tab
|
||||
sqlLab.getCurrentTab(): Tab | undefined
|
||||
|
||||
// Get all tabs
|
||||
sqlLab.getTabs(): Tab[]
|
||||
|
||||
// Get current query
|
||||
sqlLab.getCurrentQuery(): string
|
||||
|
||||
// Get selected text
|
||||
sqlLab.getSelectedText(): string | undefined
|
||||
```
|
||||
|
||||
### Database Access
|
||||
|
||||
```typescript
|
||||
// Get available databases
|
||||
sqlLab.getDatabases(): Database[]
|
||||
|
||||
// Get database by ID
|
||||
sqlLab.getDatabase(id: number): Database | undefined
|
||||
|
||||
// Get schemas for database
|
||||
sqlLab.getSchemas(databaseId: number): Promise<string[]>
|
||||
|
||||
// Get tables for schema
|
||||
sqlLab.getTables(
|
||||
databaseId: number,
|
||||
schema: string
|
||||
): Promise<Table[]>
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
```typescript
|
||||
// Query execution events
|
||||
sqlLab.onDidQueryRun: Event<QueryResult>
|
||||
sqlLab.onDidQueryStop: Event<QueryResult>
|
||||
sqlLab.onDidQueryFail: Event<QueryError>
|
||||
|
||||
// Editor events
|
||||
sqlLab.onDidChangeEditorContent: Event<string>
|
||||
sqlLab.onDidChangeSelection: Event<Selection>
|
||||
|
||||
// Tab events
|
||||
sqlLab.onDidChangeActiveTab: Event<Tab>
|
||||
sqlLab.onDidCloseTab: Event<Tab>
|
||||
sqlLab.onDidChangeTabTitle: Event<{tab: Tab, title: string}>
|
||||
|
||||
// Panel events
|
||||
sqlLab.onDidOpenPanel: Event<Panel>
|
||||
sqlLab.onDidClosePanel: Event<Panel>
|
||||
sqlLab.onDidChangeActivePanel: Event<Panel>
|
||||
```
|
||||
|
||||
**Event Usage Example:**
|
||||
```typescript
|
||||
const disposable = context.sqlLab.onDidQueryRun((result) => {
|
||||
console.log('Query executed:', result.query);
|
||||
console.log('Rows returned:', result.rowCount);
|
||||
console.log('Execution time:', result.executionTime);
|
||||
});
|
||||
|
||||
// Remember to dispose when done
|
||||
context.subscriptions.push(disposable);
|
||||
```
|
||||
|
||||
## Authentication API
|
||||
|
||||
Handles authentication and security tokens.
|
||||
|
||||
### getCSRFToken
|
||||
|
||||
Gets the current CSRF token for API requests.
|
||||
|
||||
```typescript
|
||||
authentication.getCSRFToken(): Promise<string>
|
||||
```
|
||||
|
||||
### getCurrentUser
|
||||
|
||||
Gets information about the current user.
|
||||
|
||||
```typescript
|
||||
authentication.getCurrentUser(): User
|
||||
|
||||
interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
roles: Role[];
|
||||
permissions: Permission[];
|
||||
}
|
||||
```
|
||||
|
||||
### hasPermission
|
||||
|
||||
Checks if the current user has a specific permission.
|
||||
|
||||
```typescript
|
||||
authentication.hasPermission(permission: string): boolean
|
||||
```
|
||||
|
||||
## Extensions API
|
||||
|
||||
Manages extension lifecycle and inter-extension communication.
|
||||
|
||||
### getExtension
|
||||
|
||||
Gets information about an installed extension.
|
||||
|
||||
```typescript
|
||||
extensions.getExtension(id: string): Extension | undefined
|
||||
|
||||
interface Extension {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
isActive: boolean;
|
||||
metadata: ExtensionMetadata;
|
||||
}
|
||||
```
|
||||
|
||||
### getActiveExtensions
|
||||
|
||||
Gets all currently active extensions.
|
||||
|
||||
```typescript
|
||||
extensions.getActiveExtensions(): Extension[]
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
```typescript
|
||||
// Extension lifecycle events
|
||||
extensions.onDidActivateExtension: Event<Extension>
|
||||
extensions.onDidDeactivateExtension: Event<Extension>
|
||||
```
|
||||
|
||||
## UI Components
|
||||
|
||||
Import pre-built UI components from `@apache-superset/core`:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
Button,
|
||||
Select,
|
||||
Input,
|
||||
Table,
|
||||
Modal,
|
||||
Alert,
|
||||
Tabs,
|
||||
Card,
|
||||
Dropdown,
|
||||
Menu,
|
||||
Tooltip,
|
||||
Icon,
|
||||
// ... many more
|
||||
} from '@apache-superset/core';
|
||||
```
|
||||
|
||||
### Example Component Usage
|
||||
|
||||
```typescript
|
||||
import { Button, Alert } from '@apache-superset/core';
|
||||
|
||||
function MyExtensionPanel() {
|
||||
return (
|
||||
<div>
|
||||
<Alert
|
||||
message="Extension Loaded"
|
||||
description="Your extension is ready to use"
|
||||
type="success"
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => console.log('Clicked!')}
|
||||
>
|
||||
Execute Action
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Storage API
|
||||
|
||||
Provides persistent storage for extension data.
|
||||
|
||||
### Local Storage
|
||||
|
||||
```typescript
|
||||
// Store data
|
||||
storage.local.set(key: string, value: any): Promise<void>
|
||||
|
||||
// Retrieve data
|
||||
storage.local.get(key: string): Promise<any>
|
||||
|
||||
// Remove data
|
||||
storage.local.remove(key: string): Promise<void>
|
||||
|
||||
// Clear all extension data
|
||||
storage.local.clear(): Promise<void>
|
||||
```
|
||||
|
||||
### Workspace Storage
|
||||
|
||||
Workspace storage is shared across all users for collaborative features.
|
||||
|
||||
```typescript
|
||||
storage.workspace.set(key: string, value: any): Promise<void>
|
||||
storage.workspace.get(key: string): Promise<any>
|
||||
storage.workspace.remove(key: string): Promise<void>
|
||||
```
|
||||
|
||||
## Network API
|
||||
|
||||
Utilities for making API calls to Superset.
|
||||
|
||||
### fetch
|
||||
|
||||
Enhanced fetch with CSRF token handling.
|
||||
|
||||
```typescript
|
||||
network.fetch(url: string, options?: RequestInit): Promise<Response>
|
||||
```
|
||||
|
||||
### API Client
|
||||
|
||||
Type-safe API client for Superset endpoints.
|
||||
|
||||
```typescript
|
||||
// Get chart data
|
||||
network.api.charts.get(id: number): Promise<Chart>
|
||||
|
||||
// Query database
|
||||
network.api.sqlLab.execute(
|
||||
databaseId: number,
|
||||
query: string
|
||||
): Promise<QueryResult>
|
||||
|
||||
// Get datasets
|
||||
network.api.datasets.list(): Promise<Dataset[]>
|
||||
```
|
||||
|
||||
## Utility Functions
|
||||
|
||||
### Formatting
|
||||
|
||||
```typescript
|
||||
// Format numbers
|
||||
utils.formatNumber(value: number, format?: string): string
|
||||
|
||||
// Format dates
|
||||
utils.formatDate(date: Date, format?: string): string
|
||||
|
||||
// Format SQL
|
||||
utils.formatSQL(sql: string): string
|
||||
```
|
||||
|
||||
### Validation
|
||||
|
||||
```typescript
|
||||
// Validate SQL syntax
|
||||
utils.validateSQL(sql: string): ValidationResult
|
||||
|
||||
// Check if valid database ID
|
||||
utils.isValidDatabaseId(id: any): boolean
|
||||
```
|
||||
|
||||
## TypeScript Types
|
||||
|
||||
Import common types for type safety:
|
||||
|
||||
```typescript
|
||||
import type {
|
||||
Database,
|
||||
Dataset,
|
||||
Chart,
|
||||
Dashboard,
|
||||
Query,
|
||||
QueryResult,
|
||||
Tab,
|
||||
Panel,
|
||||
User,
|
||||
Role,
|
||||
Permission,
|
||||
ExtensionContext,
|
||||
Disposable,
|
||||
Event,
|
||||
// ... more types
|
||||
} from '@apache-superset/core';
|
||||
```
|
||||
|
||||
## Extension Context
|
||||
|
||||
The context object passed to your extension's `activate` function:
|
||||
|
||||
```typescript
|
||||
interface ExtensionContext {
|
||||
// Subscription management
|
||||
subscriptions: Disposable[];
|
||||
|
||||
// Extension metadata
|
||||
extensionId: string;
|
||||
extensionPath: string;
|
||||
|
||||
// API namespaces
|
||||
core: CoreAPI;
|
||||
commands: CommandsAPI;
|
||||
sqlLab: SqlLabAPI;
|
||||
authentication: AuthenticationAPI;
|
||||
extensions: ExtensionsAPI;
|
||||
storage: StorageAPI;
|
||||
network: NetworkAPI;
|
||||
utils: UtilsAPI;
|
||||
|
||||
// Logging
|
||||
logger: Logger;
|
||||
}
|
||||
```
|
||||
|
||||
## Event Handling
|
||||
|
||||
Events follow the VS Code pattern with subscribe/dispose:
|
||||
|
||||
```typescript
|
||||
// Subscribe to event
|
||||
const disposable = sqlLab.onDidQueryRun((result) => {
|
||||
// Handle event
|
||||
});
|
||||
|
||||
// Dispose when done
|
||||
disposable.dispose();
|
||||
|
||||
// Or add to context for automatic cleanup
|
||||
context.subscriptions.push(disposable);
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always dispose subscriptions** to prevent memory leaks
|
||||
2. **Use TypeScript** for better IDE support and type safety
|
||||
3. **Handle errors gracefully** with try-catch blocks
|
||||
4. **Check permissions** before sensitive operations
|
||||
5. **Use provided UI components** for consistency
|
||||
6. **Cache API responses** when appropriate
|
||||
7. **Validate user input** before processing
|
||||
|
||||
## Version Compatibility
|
||||
|
||||
The frontend API follows semantic versioning:
|
||||
|
||||
- **Major version**: Breaking changes
|
||||
- **Minor version**: New features, backward compatible
|
||||
- **Patch version**: Bug fixes
|
||||
|
||||
Check compatibility in your `extension.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"engines": {
|
||||
"@apache-superset/core": "^1.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,348 +0,0 @@
|
||||
---
|
||||
title: Architecture Overview
|
||||
sidebar_position: 1
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
<!--
|
||||
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 Architecture Overview
|
||||
|
||||
The Superset extension architecture is designed to be modular, secure, and performant. This document provides a comprehensive overview of how extensions work and interact with the Superset host application.
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Lean Core
|
||||
Superset's core remains minimal, with features delegated to extensions wherever possible. Built-in features use the same APIs as external extensions, ensuring API quality through dogfooding.
|
||||
|
||||
### 2. Explicit Contribution Points
|
||||
All extension points are clearly defined and documented. Extensions declare their capabilities in metadata files, enabling predictable lifecycle management.
|
||||
|
||||
### 3. Versioned APIs
|
||||
Public interfaces follow semantic versioning, ensuring backward compatibility and safe evolution of the platform.
|
||||
|
||||
### 4. Lazy Loading
|
||||
Extensions load only when needed, minimizing performance impact and resource consumption.
|
||||
|
||||
### 5. Composability
|
||||
Architecture patterns and APIs are reusable across different Superset modules, promoting consistency.
|
||||
|
||||
### 6. Community-Driven
|
||||
The system evolves based on real-world feedback, with new extension points added as needs emerge.
|
||||
|
||||
## System Architecture
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Superset Host Application"
|
||||
Core[Core Application]
|
||||
API[Extension APIs]
|
||||
Loader[Extension Loader]
|
||||
Manager[Extension Manager]
|
||||
end
|
||||
|
||||
subgraph "Core Packages"
|
||||
FrontendCore["@apache-superset/core<br/>(Frontend)"]
|
||||
BackendCore["apache-superset-core<br/>(Backend)"]
|
||||
CLI["apache-superset-extensions-cli"]
|
||||
end
|
||||
|
||||
subgraph "Extension"
|
||||
Metadata[extension.json]
|
||||
Frontend[Frontend Code]
|
||||
Backend[Backend Code]
|
||||
Bundle[.supx Bundle]
|
||||
end
|
||||
|
||||
Core --> API
|
||||
API --> FrontendCore
|
||||
API --> BackendCore
|
||||
Loader --> Manager
|
||||
Manager --> Bundle
|
||||
Frontend --> FrontendCore
|
||||
Backend --> BackendCore
|
||||
CLI --> Bundle
|
||||
```
|
||||
|
||||
## Key Components
|
||||
|
||||
### Host Application
|
||||
|
||||
The Superset host application provides:
|
||||
|
||||
- **Extension APIs**: Well-defined interfaces for extensions to interact with Superset
|
||||
- **Extension Manager**: Handles lifecycle, activation, and deactivation
|
||||
- **Module Loader**: Dynamically loads extension code using Webpack Module Federation
|
||||
- **Security Context**: Manages permissions and sandboxing for extensions
|
||||
|
||||
### Core Packages
|
||||
|
||||
#### @apache-superset/core (Frontend)
|
||||
- Shared UI components and utilities
|
||||
- TypeScript type definitions
|
||||
- Frontend API implementations
|
||||
- Event system and command registry
|
||||
|
||||
#### apache-superset-core (Backend)
|
||||
- Python base classes and utilities
|
||||
- Database access APIs
|
||||
- Security and permission helpers
|
||||
- REST API registration
|
||||
|
||||
#### apache-superset-extensions-cli
|
||||
- Project scaffolding
|
||||
- Build and bundling tools
|
||||
- Development server
|
||||
- Package management
|
||||
|
||||
### Extension Structure
|
||||
|
||||
Each extension consists of:
|
||||
|
||||
- **Metadata** (`extension.json`): Declares capabilities and requirements
|
||||
- **Frontend**: React components and TypeScript code
|
||||
- **Backend**: Python modules and API endpoints
|
||||
- **Assets**: Styles, images, and other resources
|
||||
- **Bundle** (`.supx`): Packaged distribution format
|
||||
|
||||
## Module Federation
|
||||
|
||||
Extensions use Webpack Module Federation for dynamic loading:
|
||||
|
||||
```javascript
|
||||
// Extension webpack.config.js
|
||||
new ModuleFederationPlugin({
|
||||
name: 'my_extension',
|
||||
filename: 'remoteEntry.[contenthash].js',
|
||||
exposes: {
|
||||
'./index': './src/index.tsx',
|
||||
},
|
||||
externals: {
|
||||
'@apache-superset/core': 'superset',
|
||||
},
|
||||
shared: {
|
||||
react: { singleton: true },
|
||||
'react-dom': { singleton: true },
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
This allows:
|
||||
- **Independent builds**: Extensions compile separately from Superset
|
||||
- **Shared dependencies**: Common libraries like React aren't duplicated
|
||||
- **Dynamic loading**: Extensions load at runtime without rebuilding Superset
|
||||
- **Version compatibility**: Extensions declare compatible core versions
|
||||
|
||||
## Extension Lifecycle
|
||||
|
||||
### 1. Registration
|
||||
```typescript
|
||||
// Extension registered with host
|
||||
extensionManager.register({
|
||||
name: 'my-extension',
|
||||
version: '1.0.0',
|
||||
manifest: manifestData
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Activation
|
||||
```typescript
|
||||
// activate() called when extension loads
|
||||
export function activate(context: ExtensionContext) {
|
||||
// Register contributions
|
||||
const disposables = [];
|
||||
|
||||
// Add panel
|
||||
disposables.push(
|
||||
context.core.registerView('my-panel', MyPanel)
|
||||
);
|
||||
|
||||
// Register command
|
||||
disposables.push(
|
||||
context.commands.registerCommand('my-command', {
|
||||
execute: () => { /* ... */ }
|
||||
})
|
||||
);
|
||||
|
||||
// Store for cleanup
|
||||
context.subscriptions.push(...disposables);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Runtime
|
||||
- Extension responds to events
|
||||
- Provides UI components when requested
|
||||
- Executes commands when triggered
|
||||
- Accesses APIs as needed
|
||||
|
||||
### 4. Deactivation
|
||||
```typescript
|
||||
// Automatic cleanup of registered items
|
||||
export function deactivate() {
|
||||
// context.subscriptions automatically disposed
|
||||
// Additional cleanup if needed
|
||||
}
|
||||
```
|
||||
|
||||
## Contribution Types
|
||||
|
||||
### Views
|
||||
Extensions can add panels and UI components:
|
||||
|
||||
```json
|
||||
{
|
||||
"views": {
|
||||
"sqllab.panels": [{
|
||||
"id": "my-panel",
|
||||
"name": "My Panel",
|
||||
"icon": "ToolOutlined"
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Commands
|
||||
Define executable actions:
|
||||
|
||||
```json
|
||||
{
|
||||
"commands": [{
|
||||
"command": "my-extension.run",
|
||||
"title": "Run Analysis",
|
||||
"icon": "PlayCircleOutlined"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### Menus
|
||||
Add items to existing menus:
|
||||
|
||||
```json
|
||||
{
|
||||
"menus": {
|
||||
"sqllab.editor": {
|
||||
"primary": [{
|
||||
"command": "my-extension.run",
|
||||
"when": "editorHasSelection"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### API Endpoints
|
||||
Register backend REST endpoints:
|
||||
|
||||
```python
|
||||
from superset_core.api import rest_api
|
||||
|
||||
@rest_api.route('/my-endpoint')
|
||||
def my_endpoint():
|
||||
return {'data': 'value'}
|
||||
```
|
||||
|
||||
## Security Model
|
||||
|
||||
### Permissions
|
||||
- Extensions run with user's permissions
|
||||
- No elevation of privileges
|
||||
- Access controlled by Superset's RBAC
|
||||
|
||||
### Sandboxing
|
||||
- Frontend code runs in browser context
|
||||
- Backend code runs in Python process
|
||||
- Future: Optional sandboxed execution
|
||||
|
||||
### Validation
|
||||
- Manifest validation on upload
|
||||
- Signature verification (future)
|
||||
- Dependency scanning
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Lazy Loading
|
||||
- Extensions load only when features are accessed
|
||||
- Code splitting for large extensions
|
||||
- Cached after first load
|
||||
|
||||
### Bundle Optimization
|
||||
- Tree shaking removes unused code
|
||||
- Minification reduces size
|
||||
- Compression for network transfer
|
||||
|
||||
### Resource Management
|
||||
- Automatic cleanup on deactivation
|
||||
- Memory leak prevention
|
||||
- Event listener management
|
||||
|
||||
## Development vs Production
|
||||
|
||||
### Development Mode
|
||||
```python
|
||||
# superset_config.py
|
||||
ENABLE_EXTENSIONS = True
|
||||
LOCAL_EXTENSIONS = ['/path/to/extension']
|
||||
```
|
||||
- Hot reloading
|
||||
- Source maps
|
||||
- Debug logging
|
||||
|
||||
### Production Mode
|
||||
- Optimized bundles
|
||||
- Cached assets
|
||||
- Performance monitoring
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Planned Features
|
||||
- Enhanced sandboxing
|
||||
- Extension marketplace
|
||||
- Inter-extension communication
|
||||
- Theme contributions
|
||||
- Chart type extensions
|
||||
|
||||
### API Expansion
|
||||
- Dashboard extensions
|
||||
- Database connector API
|
||||
- Security provider interface
|
||||
- Workflow automation
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
- ✅ Use TypeScript for type safety
|
||||
- ✅ Follow semantic versioning
|
||||
- ✅ Handle errors gracefully
|
||||
- ✅ Clean up resources properly
|
||||
- ✅ Document your extension
|
||||
|
||||
### Don'ts
|
||||
- ❌ Access private APIs
|
||||
- ❌ Modify global state directly
|
||||
- ❌ Block the main thread
|
||||
- ❌ Store sensitive data insecurely
|
||||
- ❌ Assume API stability in 0.x versions
|
||||
|
||||
## Learn More
|
||||
|
||||
- [API Reference](../api/frontend)
|
||||
- [Development Guide](../getting-started)
|
||||
- [Security Guidelines](./security)
|
||||
- [Performance Optimization](./performance)
|
||||
@@ -1,466 +0,0 @@
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
---
|
||||
title: CLI Documentation
|
||||
sidebar_position: 1
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
# Superset Extensions CLI
|
||||
|
||||
The `apache-superset-extensions-cli` provides command-line tools for creating, developing, and packaging Superset extensions.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install apache-superset-extensions-cli
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### init
|
||||
|
||||
Creates a new extension project with the standard folder structure.
|
||||
|
||||
```bash
|
||||
superset-extensions init <extension-name> [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--template <template>`: Use a specific template (default: basic)
|
||||
- `--author <name>`: Set the author name
|
||||
- `--description <text>`: Set the extension description
|
||||
- `--with-backend`: Include backend code structure
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
superset-extensions init my-extension \
|
||||
--author "John Doe" \
|
||||
--description "Adds custom analytics to SQL Lab" \
|
||||
--with-backend
|
||||
```
|
||||
|
||||
**Generated Structure:**
|
||||
```
|
||||
my-extension/
|
||||
├── extension.json
|
||||
├── frontend/
|
||||
│ ├── src/
|
||||
│ │ └── index.tsx
|
||||
│ ├── package.json
|
||||
│ ├── tsconfig.json
|
||||
│ └── webpack.config.js
|
||||
├── backend/
|
||||
│ ├── src/
|
||||
│ │ └── my_extension/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── entrypoint.py
|
||||
│ ├── tests/
|
||||
│ ├── pyproject.toml
|
||||
│ └── requirements.txt
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### dev
|
||||
|
||||
Starts the development server with hot reloading.
|
||||
|
||||
```bash
|
||||
superset-extensions dev [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--port <port>`: Development server port (default: 9001)
|
||||
- `--host <host>`: Development server host (default: localhost)
|
||||
- `--no-watch`: Disable file watching
|
||||
- `--verbose`: Show detailed output
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Start development server
|
||||
superset-extensions dev
|
||||
|
||||
# Output:
|
||||
⚙️ Building frontend assets...
|
||||
✅ Frontend rebuilt
|
||||
✅ Backend files synced
|
||||
✅ Manifest updated
|
||||
👀 Watching for changes...
|
||||
```
|
||||
|
||||
### build
|
||||
|
||||
Builds the extension for production.
|
||||
|
||||
```bash
|
||||
superset-extensions build [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--mode <mode>`: Build mode (development | production)
|
||||
- `--analyze`: Generate bundle analysis
|
||||
- `--source-maps`: Include source maps
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Production build
|
||||
superset-extensions build --mode production
|
||||
|
||||
# With analysis
|
||||
superset-extensions build --analyze
|
||||
```
|
||||
|
||||
### bundle
|
||||
|
||||
Creates a `.supx` package for distribution.
|
||||
|
||||
```bash
|
||||
superset-extensions bundle [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--output <path>`: Output directory (default: current)
|
||||
- `--sign`: Sign the package (requires certificate)
|
||||
- `--compress`: Compression level (0-9, default: 6)
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Create bundle
|
||||
superset-extensions bundle
|
||||
|
||||
# Creates: my-extension-1.0.0.supx
|
||||
```
|
||||
|
||||
### validate
|
||||
|
||||
Validates extension configuration and structure.
|
||||
|
||||
```bash
|
||||
superset-extensions validate [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--fix`: Auto-fix common issues
|
||||
- `--strict`: Enable strict validation
|
||||
|
||||
**Checks:**
|
||||
- Valid extension.json syntax
|
||||
- Required files present
|
||||
- Dependency versions
|
||||
- Module exports
|
||||
- TypeScript configuration
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
superset-extensions validate --strict
|
||||
|
||||
# Output:
|
||||
✅ extension.json valid
|
||||
✅ Frontend structure valid
|
||||
✅ Backend structure valid
|
||||
⚠️ Warning: Missing LICENSE file
|
||||
✅ Validation passed with warnings
|
||||
```
|
||||
|
||||
### test
|
||||
|
||||
Runs extension tests.
|
||||
|
||||
```bash
|
||||
superset-extensions test [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--coverage`: Generate coverage report
|
||||
- `--watch`: Run in watch mode
|
||||
- `--frontend-only`: Run only frontend tests
|
||||
- `--backend-only`: Run only backend tests
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Run all tests
|
||||
superset-extensions test --coverage
|
||||
|
||||
# Watch mode for frontend
|
||||
superset-extensions test --frontend-only --watch
|
||||
```
|
||||
|
||||
### publish
|
||||
|
||||
Publishes extension to a registry (future feature).
|
||||
|
||||
```bash
|
||||
superset-extensions publish [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--registry <url>`: Registry URL
|
||||
- `--token <token>`: Authentication token
|
||||
- `--dry-run`: Simulate publish
|
||||
|
||||
## Configuration
|
||||
|
||||
### Project Configuration
|
||||
|
||||
The CLI reads configuration from multiple sources:
|
||||
|
||||
1. **extension.json** - Extension metadata
|
||||
2. **package.json** - Frontend dependencies
|
||||
3. **pyproject.toml** - Backend configuration
|
||||
4. **.extensionrc** - CLI-specific settings
|
||||
|
||||
### .extensionrc Example
|
||||
|
||||
```json
|
||||
{
|
||||
"dev": {
|
||||
"port": 9001,
|
||||
"host": "localhost",
|
||||
"autoReload": true
|
||||
},
|
||||
"build": {
|
||||
"mode": "production",
|
||||
"sourceMaps": false,
|
||||
"optimization": true
|
||||
},
|
||||
"test": {
|
||||
"coverage": true,
|
||||
"threshold": {
|
||||
"statements": 80,
|
||||
"branches": 70,
|
||||
"functions": 80,
|
||||
"lines": 80
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Templates
|
||||
|
||||
### Available Templates
|
||||
|
||||
- **basic**: Simple extension with frontend only
|
||||
- **full-stack**: Frontend and backend components
|
||||
- **sql-panel**: SQL Lab panel extension
|
||||
- **api-only**: Backend API extension
|
||||
- **chart-plugin**: Custom chart visualization
|
||||
|
||||
### Using Templates
|
||||
|
||||
```bash
|
||||
# Use specific template
|
||||
superset-extensions init my-chart --template chart-plugin
|
||||
|
||||
# List available templates
|
||||
superset-extensions init --list-templates
|
||||
```
|
||||
|
||||
### Custom Templates
|
||||
|
||||
Create custom templates in `~/.superset-extensions/templates/`:
|
||||
|
||||
```
|
||||
~/.superset-extensions/templates/
|
||||
└── my-template/
|
||||
├── template.json
|
||||
└── files/
|
||||
└── ... template files ...
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### 1. Create Extension
|
||||
|
||||
```bash
|
||||
superset-extensions init awesome-feature
|
||||
cd awesome-feature
|
||||
```
|
||||
|
||||
### 2. Install Dependencies
|
||||
|
||||
```bash
|
||||
# Frontend
|
||||
cd frontend && npm install
|
||||
|
||||
# Backend (if applicable)
|
||||
cd ../backend && pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 3. Configure Superset
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
ENABLE_EXTENSIONS = True
|
||||
LOCAL_EXTENSIONS = [
|
||||
"/path/to/awesome-feature"
|
||||
]
|
||||
```
|
||||
|
||||
### 4. Start Development
|
||||
|
||||
```bash
|
||||
# Terminal 1: Extension dev server
|
||||
superset-extensions dev
|
||||
|
||||
# Terminal 2: Superset
|
||||
superset run -p 8088 --reload
|
||||
```
|
||||
|
||||
### 5. Test Changes
|
||||
|
||||
Make changes to your code and see them reflected immediately in Superset.
|
||||
|
||||
### 6. Build and Package
|
||||
|
||||
```bash
|
||||
# Validate
|
||||
superset-extensions validate
|
||||
|
||||
# Test
|
||||
superset-extensions test
|
||||
|
||||
# Build
|
||||
superset-extensions build --mode production
|
||||
|
||||
# Bundle
|
||||
superset-extensions bundle
|
||||
```
|
||||
|
||||
### 7. Deploy
|
||||
|
||||
Upload the `.supx` file to your Superset instance.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The CLI respects these environment variables:
|
||||
|
||||
- `SUPERSET_EXTENSIONS_DEV_PORT`: Development server port
|
||||
- `SUPERSET_EXTENSIONS_DEV_HOST`: Development server host
|
||||
- `SUPERSET_BASE_URL`: Superset instance URL
|
||||
- `NODE_ENV`: Node environment (development/production)
|
||||
- `PYTHONPATH`: Python module search path
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Port Already in Use
|
||||
|
||||
```bash
|
||||
# Use different port
|
||||
superset-extensions dev --port 9002
|
||||
```
|
||||
|
||||
#### Module Federation Errors
|
||||
|
||||
```bash
|
||||
# Rebuild with clean cache
|
||||
rm -rf dist/ node_modules/.cache
|
||||
superset-extensions build
|
||||
```
|
||||
|
||||
#### Python Import Errors
|
||||
|
||||
```bash
|
||||
# Ensure virtual environment is activated
|
||||
source venv/bin/activate
|
||||
superset-extensions dev
|
||||
```
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable verbose output for troubleshooting:
|
||||
|
||||
```bash
|
||||
# Verbose output
|
||||
superset-extensions dev --verbose
|
||||
|
||||
# Debug webpack
|
||||
DEBUG=webpack:* superset-extensions build
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Version Control**: Commit `extension.json` but not `dist/`
|
||||
2. **Dependencies**: Pin versions in package.json
|
||||
3. **Testing**: Write tests for critical functionality
|
||||
4. **Documentation**: Keep README.md updated
|
||||
5. **Validation**: Run validate before bundling
|
||||
6. **Semantic Versioning**: Follow semver for releases
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Custom Webpack Configuration
|
||||
|
||||
Extend the default webpack config:
|
||||
|
||||
```javascript
|
||||
// webpack.config.js
|
||||
const baseConfig = require('./webpack.base.config');
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
// Custom modifications
|
||||
resolve: {
|
||||
...baseConfig.resolve,
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### CI/CD Integration
|
||||
|
||||
```yaml
|
||||
# .github/workflows/extension.yml
|
||||
name: Extension CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-python@v2
|
||||
|
||||
- name: Install CLI
|
||||
run: pip install apache-superset-extensions-cli
|
||||
|
||||
- name: Validate
|
||||
run: superset-extensions validate --strict
|
||||
|
||||
- name: Test
|
||||
run: superset-extensions test --coverage
|
||||
|
||||
- name: Build
|
||||
run: superset-extensions build --mode production
|
||||
|
||||
- name: Bundle
|
||||
run: superset-extensions bundle
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
- **Documentation**: [Developer Portal](../)
|
||||
- **Examples**: [GitHub Repository](https://github.com/apache/superset/tree/master/extensions)
|
||||
- **Issues**: [GitHub Issues](https://github.com/apache/superset/issues)
|
||||
- **Community**: [Slack Channel](https://apache-superset.slack.com)
|
||||
339
docs/developer_portal/contributing/code-review.md
Normal file
339
docs/developer_portal/contributing/code-review.md
Normal file
@@ -0,0 +1,339 @@
|
||||
---
|
||||
title: Code Review Process
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Code Review Process
|
||||
|
||||
Understand how code reviews work in Apache Superset and how to participate effectively.
|
||||
|
||||
## Overview
|
||||
|
||||
Code review is a critical part of maintaining code quality and sharing knowledge across the team. Every change to Superset goes through peer review before merging.
|
||||
|
||||
## For Authors
|
||||
|
||||
### Preparing for Review
|
||||
|
||||
#### Before Requesting Review
|
||||
- [ ] Self-review your changes
|
||||
- [ ] Ensure CI checks pass
|
||||
- [ ] Add comprehensive tests
|
||||
- [ ] Update documentation
|
||||
- [ ] Fill out PR template completely
|
||||
- [ ] Add screenshots for UI changes
|
||||
|
||||
#### Self-Review Checklist
|
||||
```bash
|
||||
# View your changes
|
||||
git diff upstream/master
|
||||
|
||||
# Check for common issues:
|
||||
# - Commented out code
|
||||
# - Debug statements (console.log, print)
|
||||
# - TODO comments that should be addressed
|
||||
# - Hardcoded values that should be configurable
|
||||
# - Missing error handling
|
||||
# - Performance implications
|
||||
```
|
||||
|
||||
### Requesting Review
|
||||
|
||||
#### Auto-Assignment
|
||||
GitHub will automatically request reviews based on CODEOWNERS file.
|
||||
|
||||
#### Manual Assignment
|
||||
For specific expertise, request additional reviewers:
|
||||
- Frontend changes: Tag frontend experts
|
||||
- Backend changes: Tag backend experts
|
||||
- Security changes: Tag security team
|
||||
- Database changes: Tag database experts
|
||||
|
||||
#### Review Request Message
|
||||
```markdown
|
||||
@reviewer This PR implements [feature]. Could you please review:
|
||||
1. The approach taken in [file]
|
||||
2. Performance implications of [change]
|
||||
3. Security considerations for [feature]
|
||||
|
||||
Thanks!
|
||||
```
|
||||
|
||||
### Responding to Feedback
|
||||
|
||||
#### Best Practices
|
||||
- **Be receptive**: Reviews improve code quality
|
||||
- **Ask questions**: Clarify if feedback is unclear
|
||||
- **Explain decisions**: Share context for your choices
|
||||
- **Update promptly**: Address feedback in timely manner
|
||||
|
||||
#### Comment Responses
|
||||
```markdown
|
||||
# Acknowledging
|
||||
"Good catch! Fixed in [commit hash]"
|
||||
|
||||
# Explaining
|
||||
"I chose this approach because [reason]. Would you prefer [alternative]?"
|
||||
|
||||
# Questioning
|
||||
"Could you elaborate on [concern]? I'm not sure I understand the issue."
|
||||
|
||||
# Disagreeing respectfully
|
||||
"I see your point, but I think [current approach] because [reason]. What do you think?"
|
||||
```
|
||||
|
||||
## For Reviewers
|
||||
|
||||
### Review Responsibilities
|
||||
|
||||
#### What to Review
|
||||
1. **Correctness**: Does the code do what it claims?
|
||||
2. **Design**: Is the approach appropriate?
|
||||
3. **Clarity**: Is the code readable and maintainable?
|
||||
4. **Testing**: Are tests comprehensive?
|
||||
5. **Performance**: Any performance concerns?
|
||||
6. **Security**: Any security issues?
|
||||
7. **Documentation**: Is it well documented?
|
||||
|
||||
### Review Checklist
|
||||
|
||||
#### Functionality
|
||||
- [ ] Feature works as described
|
||||
- [ ] Edge cases are handled
|
||||
- [ ] Error handling is appropriate
|
||||
- [ ] Backwards compatibility maintained
|
||||
|
||||
#### Code Quality
|
||||
- [ ] Follows project conventions
|
||||
- [ ] No code duplication
|
||||
- [ ] Clear variable/function names
|
||||
- [ ] Appropriate abstraction levels
|
||||
- [ ] SOLID principles followed
|
||||
|
||||
#### Testing
|
||||
- [ ] Unit tests for business logic
|
||||
- [ ] Integration tests for APIs
|
||||
- [ ] E2E tests for critical paths
|
||||
- [ ] Tests are maintainable
|
||||
- [ ] Good test coverage
|
||||
|
||||
#### Security
|
||||
- [ ] Input validation
|
||||
- [ ] SQL injection prevention
|
||||
- [ ] XSS prevention
|
||||
- [ ] CSRF protection
|
||||
- [ ] Authentication/authorization checks
|
||||
- [ ] No sensitive data in logs
|
||||
|
||||
#### Performance
|
||||
- [ ] Database queries optimized
|
||||
- [ ] No N+1 queries
|
||||
- [ ] Appropriate caching
|
||||
- [ ] Frontend bundle size impact
|
||||
- [ ] Memory usage considerations
|
||||
|
||||
### Providing Feedback
|
||||
|
||||
#### Effective Comments
|
||||
|
||||
```python
|
||||
# ✅ Good: Specific and actionable
|
||||
"This query could cause N+1 problems. Consider using
|
||||
`select_related('user')` to fetch users in a single query."
|
||||
|
||||
# ❌ Bad: Vague
|
||||
"This doesn't look right."
|
||||
```
|
||||
|
||||
```typescript
|
||||
// ✅ Good: Suggests improvement
|
||||
"Consider using useMemo here to prevent unnecessary
|
||||
re-renders when dependencies haven't changed."
|
||||
|
||||
// ❌ Bad: Just criticism
|
||||
"This is inefficient."
|
||||
```
|
||||
|
||||
#### Comment Types
|
||||
|
||||
**Use GitHub's comment types:**
|
||||
- **Comment**: General feedback or questions
|
||||
- **Approve**: Changes look good
|
||||
- **Request Changes**: Must be addressed before merge
|
||||
|
||||
**Prefix conventions:**
|
||||
- `nit:` Minor issue (non-blocking)
|
||||
- `suggestion:` Recommended improvement
|
||||
- `question:` Seeking clarification
|
||||
- `blocker:` Must be fixed
|
||||
- `praise:` Highlighting good work
|
||||
|
||||
#### Examples
|
||||
|
||||
```markdown
|
||||
nit: Consider renaming `getData` to `fetchUserData` for clarity
|
||||
|
||||
suggestion: This could be simplified using Array.reduce()
|
||||
|
||||
question: Is this intentionally not handling the error case?
|
||||
|
||||
blocker: This SQL is vulnerable to injection. Please use parameterized queries.
|
||||
|
||||
praise: Excellent test coverage! 👍
|
||||
```
|
||||
|
||||
## Review Process
|
||||
|
||||
### Timeline
|
||||
|
||||
#### Expected Response Times
|
||||
- **Initial review**: Within 2-3 business days
|
||||
- **Follow-up review**: Within 1-2 business days
|
||||
- **Critical fixes**: ASAP (tag in Slack)
|
||||
|
||||
#### Escalation
|
||||
If no response after 3 days:
|
||||
1. Ping reviewer in PR comments
|
||||
2. Ask in #development Slack channel
|
||||
3. Tag @apache/superset-committers
|
||||
|
||||
### Approval Requirements
|
||||
|
||||
#### Minimum Requirements
|
||||
- **1 approval** from a committer for minor changes
|
||||
- **2 approvals** for significant features
|
||||
- **3 approvals** for breaking changes
|
||||
|
||||
#### Special Cases
|
||||
- **Security changes**: Require security team review
|
||||
- **API changes**: Require API team review
|
||||
- **Database migrations**: Require database expert review
|
||||
- **UI/UX changes**: Require design review
|
||||
|
||||
### Merge Process
|
||||
|
||||
#### Who Can Merge
|
||||
- Committers with write access
|
||||
- After all requirements met
|
||||
- CI checks must pass
|
||||
|
||||
#### Merge Methods
|
||||
- **Squash and merge**: Default for feature PRs
|
||||
- **Rebase and merge**: For clean history
|
||||
- **Create merge commit**: Rarely used
|
||||
|
||||
#### Merge Checklist
|
||||
- [ ] All CI checks green
|
||||
- [ ] Required approvals obtained
|
||||
- [ ] No unresolved conversations
|
||||
- [ ] PR title follows conventions
|
||||
- [ ] Milestone set (if applicable)
|
||||
|
||||
## Review Etiquette
|
||||
|
||||
### Do's
|
||||
- ✅ Be kind and constructive
|
||||
- ✅ Acknowledge time and effort
|
||||
- ✅ Provide specific examples
|
||||
- ✅ Suggest solutions
|
||||
- ✅ Praise good work
|
||||
- ✅ Consider cultural differences
|
||||
- ✅ Focus on the code, not the person
|
||||
|
||||
### Don'ts
|
||||
- ❌ Use harsh or dismissive language
|
||||
- ❌ Bikeshed on minor preferences
|
||||
- ❌ Review when tired or frustrated
|
||||
- ❌ Make personal attacks
|
||||
- ❌ Ignore the PR description
|
||||
- ❌ Demand perfection
|
||||
|
||||
## Becoming a Reviewer
|
||||
|
||||
### Path to Reviewer
|
||||
1. **Contribute regularly**: Submit quality PRs
|
||||
2. **Participate in discussions**: Share knowledge
|
||||
3. **Review others' code**: Start with comments
|
||||
4. **Build expertise**: Focus on specific areas
|
||||
5. **Get nominated**: By existing committers
|
||||
|
||||
### Reviewer Expectations
|
||||
- Review PRs in your area of expertise
|
||||
- Respond within reasonable time
|
||||
- Mentor new contributors
|
||||
- Maintain high standards
|
||||
- Stay current with best practices
|
||||
|
||||
## Advanced Topics
|
||||
|
||||
### Reviewing Large PRs
|
||||
|
||||
#### Strategy
|
||||
1. **Request splitting**: Ask to break into smaller PRs
|
||||
2. **Review in phases**:
|
||||
- Architecture/approach first
|
||||
- Implementation details second
|
||||
- Tests and docs last
|
||||
3. **Use draft reviews**: Save comments and submit together
|
||||
|
||||
### Cross-Team Reviews
|
||||
|
||||
#### When Needed
|
||||
- Changes affecting multiple teams
|
||||
- Shared components/libraries
|
||||
- API contract changes
|
||||
- Database schema changes
|
||||
|
||||
### Performance Reviews
|
||||
|
||||
#### Tools
|
||||
```python
|
||||
# Backend performance
|
||||
import cProfile
|
||||
import pstats
|
||||
|
||||
# Profile the code
|
||||
cProfile.run('function_to_profile()', 'stats.prof')
|
||||
stats = pstats.Stats('stats.prof')
|
||||
stats.sort_stats('cumulative').print_stats(10)
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Frontend performance
|
||||
// Use React DevTools Profiler
|
||||
// Chrome DevTools Performance tab
|
||||
// Lighthouse audits
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
### Internal
|
||||
- [Coding Guidelines](../guidelines/design-guidelines)
|
||||
- [Testing Guide](../testing/overview)
|
||||
- [Extension Architecture](../extensions/architecture)
|
||||
|
||||
### External
|
||||
- [Google's Code Review Guide](https://google.github.io/eng-practices/review/)
|
||||
- [Best Practices for Code Review](https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/)
|
||||
- [The Art of Readable Code](https://www.oreilly.com/library/view/the-art-of/9781449318482/)
|
||||
|
||||
Next: [Reporting issues effectively](./issue-reporting)
|
||||
1108
docs/developer_portal/contributing/development-setup.md
Normal file
1108
docs/developer_portal/contributing/development-setup.md
Normal file
File diff suppressed because it is too large
Load Diff
415
docs/developer_portal/contributing/guidelines.md
Normal file
415
docs/developer_portal/contributing/guidelines.md
Normal file
@@ -0,0 +1,415 @@
|
||||
---
|
||||
title: Contribution Guidelines
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Contribution Guidelines
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
A philosophy we would like to strongly encourage is
|
||||
|
||||
> Before creating a PR, create an issue.
|
||||
|
||||
The purpose is to separate problem from possible solutions.
|
||||
|
||||
**Bug fixes:** If you're only fixing a small bug, it's fine to submit a pull request right away but we highly recommend filing an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue. Please keep in mind that the project maintainers reserve the rights to accept or reject incoming PRs, so it is better to separate the issue and the code to fix it from each other. In some cases, project maintainers may request you to create a separate issue from PR before proceeding.
|
||||
|
||||
**Refactor:** For small refactors, it can be a standalone PR itself detailing what you are refactoring and why. If there are concerns, project maintainers may request you to create a `#SIP` for the PR before proceeding.
|
||||
|
||||
**Feature/Large changes:** If you intend to change the public API, or make any non-trivial changes to the implementation, we require you to file a new issue as `#SIP` (Superset Improvement Proposal). This lets us reach an agreement on your proposal before you put significant effort into it. You are welcome to submit a PR along with the SIP (sometimes necessary for demonstration), but we will not review/merge the code until the SIP is approved.
|
||||
|
||||
In general, small PRs are always easier to review than large PRs. The best practice is to break your work into smaller independent PRs and refer to the same issue. This will greatly reduce turnaround time.
|
||||
|
||||
If you wish to share your work which is not ready to merge yet, create a [Draft PR](https://github.blog/2019-02-14-introducing-draft-pull-requests/). This will enable maintainers and the CI runner to prioritize mature PR's.
|
||||
|
||||
Finally, never submit a PR that will put master branch in broken state. If the PR is part of multiple PRs to complete a large feature and cannot work on its own, you can create a feature branch and merge all related PRs into the feature branch before creating a PR from feature branch to master.
|
||||
|
||||
### Protocol
|
||||
|
||||
#### Authoring
|
||||
|
||||
- Fill in all sections of the PR template.
|
||||
- Title the PR with one of the following semantic prefixes (inspired by [Karma](http://karma-runner.github.io/0.10/dev/git-commit-msg.html])):
|
||||
|
||||
- `feat` (new feature)
|
||||
- `fix` (bug fix)
|
||||
- `docs` (changes to the documentation)
|
||||
- `style` (formatting, missing semi colons, etc; no application logic change)
|
||||
- `refactor` (refactoring code)
|
||||
- `test` (adding missing tests, refactoring tests; no application logic change)
|
||||
- `chore` (updating tasks etc; no application logic change)
|
||||
- `perf` (performance-related change)
|
||||
- `build` (build tooling, Docker configuration change)
|
||||
- `ci` (test runner, GitHub Actions workflow changes)
|
||||
- `other` (changes that don't correspond to the above -- should be rare!)
|
||||
- Examples:
|
||||
- `feat: export charts as ZIP files`
|
||||
- `perf(api): improve API info performance`
|
||||
- `fix(chart-api): cached-indicator always shows value is cached`
|
||||
|
||||
- Add prefix `[WIP]` to title if not ready for review (WIP = work-in-progress). We recommend creating a PR with `[WIP]` first and remove it once you have passed CI test and read through your code changes at least once.
|
||||
- If you believe your PR contributes a potentially breaking change, put a `!` after the semantic prefix but before the colon in the PR title, like so: `feat!: Added foo functionality to bar`
|
||||
- **Screenshots/GIFs:** Changes to user interface require before/after screenshots, or GIF for interactions
|
||||
- Recommended capture tools ([Kap](https://getkap.co/), [LICEcap](https://www.cockos.com/licecap/), [Skitch](https://download.cnet.com/Skitch/3000-13455_4-189876.html))
|
||||
- If no screenshot is provided, the committers will mark the PR with `need:screenshot` label and will not review until screenshot is provided.
|
||||
- **Dependencies:** Be careful about adding new dependency and avoid unnecessary dependencies.
|
||||
- For Python, include it in `pyproject.toml` denoting any specific restrictions and
|
||||
in `requirements.txt` pinned to a specific version which ensures that the application
|
||||
build is deterministic.
|
||||
- For TypeScript/JavaScript, include new libraries in `package.json`
|
||||
- **Tests:** The pull request should include tests, either as doctests, unit tests, or both. Make sure to resolve all errors and test failures. See [Testing](./howtos#testing) for how to run tests.
|
||||
- **Documentation:** If the pull request adds functionality, the docs should be updated as part of the same PR.
|
||||
- **CI:** Reviewers will not review the code until all CI tests are passed. Sometimes there can be flaky tests. You can close and open PR to re-run CI test. Please report if the issue persists. After the CI fix has been deployed to `master`, please rebase your PR.
|
||||
- **Code coverage:** Please ensure that code coverage does not decrease.
|
||||
- Remove `[WIP]` when ready for review. Please note that it may be merged soon after approved so please make sure the PR is ready to merge and do not expect more time for post-approval edits.
|
||||
- If the PR was not ready for review and inactive for > 30 days, we will close it due to inactivity. The author is welcome to re-open and update.
|
||||
|
||||
#### Reviewing
|
||||
|
||||
- Use constructive tone when writing reviews.
|
||||
- If there are changes required, state clearly what needs to be done before the PR can be approved.
|
||||
- If you are asked to update your pull request with some changes there's no need to create a new one. Push your changes to the same branch.
|
||||
- The committers reserve the right to reject any PR and in some cases may request the author to file an issue.
|
||||
|
||||
#### Test Environments
|
||||
|
||||
- Members of the Apache GitHub org can launch an ephemeral test environment directly on a pull request by creating a comment containing (only) the command `/testenv up`.
|
||||
- Note that org membership must be public in order for this validation to function properly.
|
||||
- Feature flags may be set for a test environment by specifying the flag name (prefixed with `FEATURE_`) and value after the command.
|
||||
- Format: `/testenv up FEATURE_<feature flag name>=true|false`
|
||||
- Example: `/testenv up FEATURE_DASHBOARD_NATIVE_FILTERS=true`
|
||||
- Multiple feature flags may be set in single command, separated by whitespace
|
||||
- A comment will be created by the workflow script with the address and login information for the ephemeral environment.
|
||||
- Test environments may be created once the Docker build CI workflow for the PR has completed successfully.
|
||||
- Test environments do not currently update automatically when new commits are added to a pull request.
|
||||
- Test environments do not currently support async workers, though this is planned.
|
||||
- Running test environments will be shutdown upon closing the pull request.
|
||||
|
||||
You can also access per-PR ephemeral environment directly using the following URL pattern:
|
||||
`https://pr-{PR_NUMBER}.superset.apache.org`
|
||||
|
||||
#### Merging
|
||||
|
||||
- At least one approval is required for merging a PR.
|
||||
- PR is usually left open for at least 24 hours before merging.
|
||||
- After the PR is merged, [close the corresponding issue(s)](https://help.github.com/articles/closing-issues-using-keywords/).
|
||||
|
||||
#### Post-merge Responsibility
|
||||
|
||||
- Project maintainers may contact the PR author if new issues are introduced by the PR.
|
||||
- Project maintainers may revert your changes if a critical issue is found, such as breaking master branch CI.
|
||||
|
||||
## Managing Issues and PRs
|
||||
|
||||
To handle issues and PRs that are coming in, committers read issues/PRs and flag them with labels to categorize and help contributors spot where to take actions, as contributors usually have different expertises.
|
||||
|
||||
Triaging goals
|
||||
|
||||
- **For issues:** Categorize, screen issues, flag required actions from authors.
|
||||
- **For PRs:** Categorize, flag required actions from authors. If PR is ready for review, flag required actions from reviewers.
|
||||
|
||||
First, add **Category labels (a.k.a. hash labels)**. Every issue/PR must have one hash label (except spam entry). Labels that begin with `#` defines issue/PR type:
|
||||
|
||||
| Label | for Issue | for PR |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `#bug` | Bug report | Bug fix |
|
||||
| `#code-quality` | Describe problem with code, architecture or productivity | Refactor, tests, tooling |
|
||||
| `#feature` | New feature request | New feature implementation |
|
||||
| `#refine` | Propose improvement such as adjusting padding or refining UI style, excluding new features, bug fixes, and refactoring. | Implementation of improvement such as adjusting padding or refining UI style, excluding new features, bug fixes, and refactoring. |
|
||||
| `#doc` | Documentation | Documentation |
|
||||
| `#question` | Troubleshooting: Installation, Running locally, Ask how to do something. Can be changed to `#bug` later. | N/A |
|
||||
| `#SIP` | Superset Improvement Proposal | N/A |
|
||||
| `#ASF` | Tasks related to Apache Software Foundation policy | Tasks related to Apache Software Foundation policy |
|
||||
|
||||
Then add other types of labels as appropriate.
|
||||
|
||||
- **Descriptive labels (a.k.a. dot labels):** These labels that begin with `.` describe the details of the issue/PR, such as `.ui`, `.js`, `.install`, `.backend`, etc. Each issue/PR can have zero or more dot labels.
|
||||
- **Need labels:** These labels have pattern `need:xxx`, which describe the work required to progress, such as `need:rebase`, `need:update`, `need:screenshot`.
|
||||
- **Risk labels:** These labels have pattern `risk:xxx`, which describe the potential risk on adopting the work, such as `risk:db-migration`. The intention was to better understand the impact and create awareness for PRs that need more rigorous testing.
|
||||
- **Status labels:** These labels describe the status (`abandoned`, `wontfix`, `cant-reproduce`, etc.) Issue/PRs that are rejected or closed without completion should have one or more status labels.
|
||||
- **Version labels:** These have the pattern `vx.x` such as `v0.28`. Version labels on issues describe the version the bug was reported on. Version labels on PR describe the first release that will include the PR.
|
||||
|
||||
Committers may also update title to reflect the issue/PR content if the author-provided title is not descriptive enough.
|
||||
|
||||
If the PR passes CI tests and does not have any `need:` labels, it is ready for review, add label `review` and/or `design-review`.
|
||||
|
||||
If an issue/PR has been inactive for at least 30 days, it will be closed. If it does not have any status label, add `inactive`.
|
||||
|
||||
When creating a PR, if you're aiming to have it included in a specific release, please tag it with the version label. For example, to have a PR considered for inclusion in Superset 1.1 use the label `v1.1`.
|
||||
|
||||
## Revert Guidelines
|
||||
|
||||
Reverting changes that are causing issues in the master branch is a normal and expected part of the development process. In an open source community, the ramifications of a change cannot always be fully understood. With that in mind, here are some considerations to keep in mind when considering a revert:
|
||||
|
||||
- **Availability of the PR author:** If the original PR author or the engineer who merged the code is highly available and can provide a fix in a reasonable time frame, this would counter-indicate reverting.
|
||||
- **Severity of the issue:** How severe is the problem on master? Is it keeping the project from moving forward? Is there user impact? What percentage of users will experience a problem?
|
||||
- **Size of the change being reverted:** Reverting a single small PR is a much lower-risk proposition than reverting a massive, multi-PR change.
|
||||
- **Age of the change being reverted:** Reverting a recently-merged PR will be more acceptable than reverting an older PR. A bug discovered in an older PR is unlikely to be causing widespread serious issues.
|
||||
- **Risk inherent in reverting:** Will the reversion break critical functionality? Is the medicine more dangerous than the disease?
|
||||
- **Difficulty of crafting a fix:** In the case of issues with a clear solution, it may be preferable to implement and merge a fix rather than a revert.
|
||||
|
||||
Should you decide that reverting is desirable, it is the responsibility of the Contributor performing the revert to:
|
||||
|
||||
- **Contact the interested parties:** The PR's author and the engineer who merged the work should both be contacted and informed of the revert.
|
||||
- **Provide concise reproduction steps:** Ensure that the issue can be clearly understood and duplicated by the original author of the PR.
|
||||
- **Put the revert through code review:** The revert must be approved by another committer.
|
||||
|
||||
**Revert liberally to keep `master` stable**:
|
||||
- Build failures
|
||||
- Test failures
|
||||
- Critical bugs in production
|
||||
- Security vulnerabilities
|
||||
|
||||
**How to revert**:
|
||||
1. Use GitHub's revert button when possible
|
||||
2. Create a PR with clear explanation
|
||||
3. Tag the original author
|
||||
4. Work with them on a fix
|
||||
|
||||
## Design Guidelines
|
||||
|
||||
### Capitalization Guidelines
|
||||
|
||||
#### Sentence case
|
||||
|
||||
Use sentence-case capitalization for everything in the UI (except these **).
|
||||
|
||||
Sentence case is predominantly lowercase. Capitalize only the initial character of the first word, and other words that require capitalization, like:
|
||||
|
||||
- **Proper nouns.** Objects in the product _are not_ considered proper nouns e.g. dashboards, charts, saved queries etc. Proprietary feature names eg. SQL Lab, Preset Manager _are_ considered proper nouns
|
||||
- **Acronyms** (e.g. CSS, HTML)
|
||||
- When referring to **UI labels that are themselves capitalized** from sentence case (e.g. page titles - Dashboards page, Charts page, Saved queries page, etc.)
|
||||
- User input that is reflected in the UI. E.g. a user-named a dashboard tab
|
||||
|
||||
**Sentence case vs. Title case:**
|
||||
Title case: "A Dog Takes a Walk in Paris"
|
||||
Sentence case: "A dog takes a walk in Paris"
|
||||
|
||||
**Why sentence case?**
|
||||
|
||||
- It's generally accepted as the quickest to read
|
||||
- It's the easiest form to distinguish between common and proper nouns
|
||||
|
||||
**Good examples:**
|
||||
- "Select a database"
|
||||
- "Create new chart"
|
||||
- "View all dashboards"
|
||||
|
||||
**Bad examples:**
|
||||
- "Select a Database"
|
||||
- "Create New Chart"
|
||||
- "View All Dashboards"
|
||||
|
||||
#### How to refer to UI elements
|
||||
|
||||
When writing about a UI element, use the same capitalization as used in the UI.
|
||||
|
||||
For example, if an input field is labeled "Name" then you refer to this as the "Name input field". Similarly, if a button has the label "Save" in it, then it is correct to refer to the "Save button".
|
||||
|
||||
Where a product page is titled "Settings", you refer to this in writing as follows:
|
||||
"Edit your personal information on the Settings page".
|
||||
|
||||
Often a product page will have the same title as the objects it contains. In this case, refer to the page as it appears in the UI, and the objects as common nouns:
|
||||
|
||||
- Upload a dashboard on the Dashboards page
|
||||
- Go to Dashboards
|
||||
- View dashboard
|
||||
- View all dashboards
|
||||
- Upload CSS templates on the CSS templates page
|
||||
- Queries that you save will appear on the Saved queries page
|
||||
- Create custom queries in SQL Lab then create dashboards
|
||||
|
||||
When writing about UI elements:
|
||||
- Use **bold** for clickable elements: "Click **Save**"
|
||||
- Use quotes for text fields: 'Enter "My Dashboard" in the name field'
|
||||
- Be specific about element types: button, link, dropdown, etc.
|
||||
|
||||
#### **Exceptions to sentence case
|
||||
|
||||
Only use title case for:
|
||||
- Product names (Apache Superset)
|
||||
- Proper nouns
|
||||
- Acronyms (SQL, API, CSV)
|
||||
- Input labels, buttons and UI tabs are all caps
|
||||
- User input values (e.g. column names, SQL Lab tab names) should be in their original case
|
||||
|
||||
## Programming Language Conventions
|
||||
|
||||
### Python
|
||||
|
||||
We use:
|
||||
- **[Ruff](https://docs.astral.sh/ruff/)** for linting and formatting
|
||||
- **[Mypy](http://mypy-lang.org/)** for type checking
|
||||
|
||||
Python code should:
|
||||
- Follow PEP 8
|
||||
- Use type hints for all new code
|
||||
- Use descriptive variable names
|
||||
- Include docstrings for modules, classes, and functions
|
||||
- Handle exceptions appropriately
|
||||
- Avoid global variables
|
||||
|
||||
Parameters in the `config.py` (which are accessible via the Flask app.config dictionary) are
|
||||
assumed to always be defined and thus should be accessed directly via,
|
||||
|
||||
```python
|
||||
blueprints = app.config["BLUEPRINTS"]
|
||||
```
|
||||
|
||||
rather than,
|
||||
|
||||
```python
|
||||
blueprints = app.config.get("BLUEPRINTS")
|
||||
```
|
||||
|
||||
or similar as the later will cause typing issues. The former is of type `List[Callable]`
|
||||
whereas the later is of type `Optional[List[Callable]]`.
|
||||
|
||||
#### Typing / Type Hints
|
||||
|
||||
All new Python code should include type hints:
|
||||
|
||||
To ensure clarity, consistency, all readability, _all_ new functions should use
|
||||
[type hints](https://docs.python.org/3/library/typing.html) and include a
|
||||
docstring.
|
||||
|
||||
Note per [PEP-484](https://www.python.org/dev/peps/pep-0484/#exceptions) no
|
||||
syntax for listing explicitly raised exceptions is proposed and thus the
|
||||
recommendation is to put this information in a docstring, i.e.,
|
||||
|
||||
```python
|
||||
import math
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
|
||||
|
||||
def sqrt(x: Union[float, int]) -> Union[float, int]:
|
||||
"""
|
||||
Return the square root of x.
|
||||
|
||||
:param x: A number
|
||||
:returns: The square root of the given number
|
||||
:raises ValueError: If the number is negative
|
||||
"""
|
||||
|
||||
return math.sqrt(x)
|
||||
|
||||
|
||||
def process_data(
|
||||
data: List[Dict[str, Any]],
|
||||
filter_empty: bool = True
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Process a list of data dictionaries.
|
||||
|
||||
Args:
|
||||
data: List of dictionaries containing data
|
||||
filter_empty: Whether to filter empty entries
|
||||
|
||||
Returns:
|
||||
Processed data dictionary or None if no valid data
|
||||
"""
|
||||
if not data:
|
||||
return None
|
||||
|
||||
# Process data...
|
||||
return processed_data
|
||||
```
|
||||
|
||||
Use `mypy` to check types:
|
||||
```bash
|
||||
mypy superset
|
||||
```
|
||||
|
||||
### TypeScript
|
||||
|
||||
We use:
|
||||
- **ESLint** for linting
|
||||
- **Prettier** for formatting
|
||||
- **TypeScript** strict mode
|
||||
|
||||
TypeScript is fully supported and is the recommended language for writing all new frontend
|
||||
components. When modifying existing functions/components, migrating to TypeScript is
|
||||
appreciated, but not required. Examples of migrating functions/components to TypeScript can be
|
||||
found in [#9162](https://github.com/apache/superset/pull/9162) and [#9180](https://github.com/apache/superset/pull/9180).
|
||||
|
||||
TypeScript code should:
|
||||
- Avoid `any` types - use proper TypeScript types
|
||||
- Use functional components with hooks for React
|
||||
- Include JSDoc comments for complex functions
|
||||
- Use consistent naming conventions
|
||||
- Handle errors appropriately
|
||||
|
||||
Example:
|
||||
```typescript
|
||||
interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
email?: string;
|
||||
}
|
||||
|
||||
export function processUser(user: User): string {
|
||||
// Avoid using 'any'
|
||||
const { name, email } = user;
|
||||
return email ? `${name} <${email}>` : name;
|
||||
}
|
||||
```
|
||||
|
||||
## Additional Guidelines
|
||||
|
||||
### Commit Messages
|
||||
|
||||
- Use clear, descriptive commit messages
|
||||
- Start with a verb in imperative mood
|
||||
- Reference issue numbers when applicable
|
||||
|
||||
Good: "Fix dashboard filter bug when dataset is deleted"
|
||||
Bad: "Fixed stuff"
|
||||
|
||||
### Code Review Etiquette
|
||||
|
||||
- Be respectful and constructive
|
||||
- Focus on the code, not the person
|
||||
- Provide specific suggestions for improvement
|
||||
- Acknowledge good work
|
||||
- Be open to different approaches
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update docs for any user-facing changes
|
||||
- Include code examples where helpful
|
||||
- Keep language clear and concise
|
||||
- Test documentation changes locally
|
||||
|
||||
### Security
|
||||
|
||||
- Never commit secrets or credentials
|
||||
- Validate all user input
|
||||
- Use parameterized queries for SQL
|
||||
- Follow OWASP guidelines
|
||||
- Report security issues privately to private@superset.apache.org
|
||||
|
||||
## Questions?
|
||||
|
||||
If you have questions about these guidelines, ask in:
|
||||
- [Slack #development](https://apache-superset.slack.com)
|
||||
- [GitHub Discussions](https://github.com/apache/superset/discussions)
|
||||
528
docs/developer_portal/contributing/howtos.md
Normal file
528
docs/developer_portal/contributing/howtos.md
Normal file
@@ -0,0 +1,528 @@
|
||||
---
|
||||
title: Development How-tos
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Development How-tos
|
||||
|
||||
This guide contains specific instructions for common development tasks in Superset.
|
||||
|
||||
## Contributing to Documentation
|
||||
|
||||
The documentation site is built using [Docusaurus](https://docusaurus.io/). All documentation lives in the `docs` folder, written in Markdown format.
|
||||
|
||||
### Local Development
|
||||
|
||||
To set up your local environment for documentation development:
|
||||
|
||||
```bash
|
||||
cd docs
|
||||
npm install
|
||||
npm run start
|
||||
```
|
||||
|
||||
The site will be available at http://localhost:3000
|
||||
|
||||
### Build
|
||||
|
||||
To create a production build:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm run serve # Test the build locally
|
||||
```
|
||||
|
||||
### Deployment
|
||||
|
||||
Documentation is automatically deployed when changes are merged to master.
|
||||
|
||||
## Creating Visualization Plugins
|
||||
|
||||
Visualization plugins allow you to add custom chart types to Superset. They are built as npm packages that integrate with the Superset frontend.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 18+
|
||||
- npm or yarn
|
||||
- A local Superset development environment
|
||||
|
||||
### Creating a simple Hello World viz plugin
|
||||
|
||||
1. **Install the Superset Yeoman generator**:
|
||||
```bash
|
||||
npm install -g @superset-ui/generator-superset
|
||||
```
|
||||
|
||||
2. **Create a new plugin**:
|
||||
```bash
|
||||
mkdir superset-plugin-chart-hello-world
|
||||
cd superset-plugin-chart-hello-world
|
||||
yo @superset-ui/superset
|
||||
```
|
||||
|
||||
3. **Follow the prompts**:
|
||||
- Package name: `superset-plugin-chart-hello-world`
|
||||
- Chart type: Choose your preferred type
|
||||
- Include storybook: Yes (recommended for development)
|
||||
|
||||
4. **Develop your plugin**:
|
||||
The generator creates a complete plugin structure with TypeScript, React components, and build configuration.
|
||||
|
||||
5. **Test your plugin locally**:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
6. **Link to your local Superset**:
|
||||
```bash
|
||||
npm link
|
||||
# In your Superset frontend directory:
|
||||
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.
|
||||
|
||||
## Testing
|
||||
|
||||
### Python Testing
|
||||
|
||||
Run Python tests using pytest:
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run specific test file
|
||||
pytest tests/unit_tests/test_specific.py
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=superset
|
||||
|
||||
# Run only unit tests
|
||||
pytest tests/unit_tests
|
||||
|
||||
# Run only integration tests
|
||||
pytest tests/integration_tests
|
||||
```
|
||||
|
||||
#### Testing with local Presto connections
|
||||
|
||||
To test against Presto:
|
||||
|
||||
```bash
|
||||
# Start Presto locally using Docker
|
||||
docker run -p 8080:8080 \
|
||||
--name presto \
|
||||
-d prestodb/presto
|
||||
|
||||
# Configure in superset_config.py
|
||||
SQLALCHEMY_DATABASE_URI = 'presto://localhost:8080/hive/default'
|
||||
```
|
||||
|
||||
### Frontend Testing
|
||||
|
||||
Run frontend tests using Jest:
|
||||
|
||||
```bash
|
||||
cd superset-frontend
|
||||
|
||||
# Run all tests
|
||||
npm run test
|
||||
|
||||
# Run with coverage
|
||||
npm run test -- --coverage
|
||||
|
||||
# Run in watch mode
|
||||
npm run test -- --watch
|
||||
|
||||
# Run specific test file
|
||||
npm run test -- MyComponent.test.tsx
|
||||
```
|
||||
|
||||
### E2E Integration Testing
|
||||
|
||||
We support both Playwright (recommended) and Cypress for end-to-end testing.
|
||||
|
||||
#### Playwright (Recommended - NEW)
|
||||
|
||||
Playwright is our new E2E testing framework, gradually replacing Cypress.
|
||||
|
||||
```bash
|
||||
# Navigate to frontend directory
|
||||
cd superset-frontend
|
||||
|
||||
# Run all Playwright tests
|
||||
npm run playwright:test
|
||||
# or: npx playwright test
|
||||
|
||||
# Run with interactive UI for debugging
|
||||
npm run playwright:ui
|
||||
# or: npx playwright test --ui
|
||||
|
||||
# Run in headed mode (see browser)
|
||||
npm run playwright:headed
|
||||
# or: npx playwright test --headed
|
||||
|
||||
# Run specific test file
|
||||
npx playwright test tests/auth/login.spec.ts
|
||||
|
||||
# Run with debug mode (step through tests)
|
||||
npm run playwright:debug tests/auth/login.spec.ts
|
||||
# or: npx playwright test --debug tests/auth/login.spec.ts
|
||||
|
||||
# Generate test report
|
||||
npm run playwright:report
|
||||
```
|
||||
|
||||
#### Cypress (DEPRECATED - will be removed)
|
||||
|
||||
Cypress is being phased out in favor of Playwright but is still available:
|
||||
|
||||
```bash
|
||||
# Set base URL for Cypress
|
||||
export CYPRESS_BASE_URL='http://localhost:8088'
|
||||
export CYPRESS_DATABASE=test
|
||||
export CYPRESS_USERNAME=admin
|
||||
export CYPRESS_PASSWORD=admin
|
||||
|
||||
# Navigate to Cypress directory
|
||||
cd superset-frontend/cypress-base
|
||||
|
||||
# Run interactively
|
||||
npm run cypress-debug
|
||||
|
||||
# Run headless (like CI)
|
||||
npm run cypress-run-chrome
|
||||
|
||||
# Run specific file
|
||||
npm run cypress-run-chrome -- --spec "cypress/e2e/dashboard/dashboard.test.ts"
|
||||
```
|
||||
|
||||
### Debugging Server App
|
||||
|
||||
For debugging the Flask backend:
|
||||
|
||||
#### Using PyCharm/IntelliJ
|
||||
|
||||
1. Create a new Python configuration
|
||||
2. Set script path to `superset/app.py`
|
||||
3. Set environment variables:
|
||||
- `FLASK_ENV=development`
|
||||
- `SUPERSET_CONFIG_PATH=/path/to/superset_config.py`
|
||||
4. Set breakpoints and run in debug mode
|
||||
|
||||
#### Using VS Code
|
||||
|
||||
1. Add to `.vscode/launch.json`:
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: Flask",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "flask",
|
||||
"env": {
|
||||
"FLASK_APP": "superset/app.py",
|
||||
"FLASK_ENV": "development"
|
||||
},
|
||||
"args": ["run", "--no-debugger", "--no-reload"],
|
||||
"jinja": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
2. Set breakpoints and press F5 to debug
|
||||
|
||||
### 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
|
||||
|
||||
## Contributing Translations
|
||||
|
||||
Superset uses Flask-Babel for internationalization.
|
||||
|
||||
### Enabling language selection
|
||||
|
||||
Edit `superset_config.py`:
|
||||
|
||||
```python
|
||||
LANGUAGES = {
|
||||
'en': {'flag': 'us', 'name': 'English'},
|
||||
'fr': {'flag': 'fr', 'name': 'French'},
|
||||
'zh': {'flag': 'cn', 'name': 'Chinese'},
|
||||
}
|
||||
```
|
||||
|
||||
### Creating a new language dictionary
|
||||
|
||||
```bash
|
||||
# Initialize a new language
|
||||
pybabel init -i superset/translations/messages.pot -d superset/translations -l de
|
||||
```
|
||||
|
||||
### Extracting new strings for translation
|
||||
|
||||
```bash
|
||||
# Extract Python strings
|
||||
pybabel extract -F babel.cfg -o superset/translations/messages.pot -k lazy_gettext superset
|
||||
|
||||
# Extract JavaScript strings
|
||||
npm run build-translation
|
||||
```
|
||||
|
||||
### Updating language files
|
||||
|
||||
```bash
|
||||
# Update all language files with new strings
|
||||
pybabel update -i superset/translations/messages.pot -d superset/translations
|
||||
```
|
||||
|
||||
### Applying translations
|
||||
|
||||
```bash
|
||||
# Frontend
|
||||
cd superset-frontend
|
||||
npm run build-translation
|
||||
|
||||
# Backend
|
||||
pybabel compile -d superset/translations
|
||||
```
|
||||
|
||||
## Linting
|
||||
|
||||
### Python
|
||||
|
||||
We use Ruff for Python linting and formatting:
|
||||
|
||||
```bash
|
||||
# Auto-format using ruff
|
||||
ruff format .
|
||||
|
||||
# Lint check with ruff
|
||||
ruff check .
|
||||
|
||||
# Lint fix with ruff
|
||||
ruff check --fix .
|
||||
```
|
||||
|
||||
Pre-commit hooks run automatically on `git commit` if installed.
|
||||
|
||||
### TypeScript
|
||||
|
||||
We use ESLint and Prettier for TypeScript:
|
||||
|
||||
```bash
|
||||
cd superset-frontend
|
||||
|
||||
# Run eslint checks
|
||||
npm run lint
|
||||
|
||||
# Run tsc (typescript) checks
|
||||
npm run type
|
||||
|
||||
# Fix lint issues
|
||||
npm run lint-fix
|
||||
|
||||
# Format with Prettier
|
||||
npm run prettier
|
||||
```
|
||||
|
||||
## GitHub Ephemeral Environments
|
||||
|
||||
For every PR, an ephemeral environment is automatically deployed for testing.
|
||||
|
||||
Access pattern: `https://pr-{PR_NUMBER}.superset.apache.org`
|
||||
|
||||
Features:
|
||||
- Automatically deployed on PR creation/update
|
||||
- Includes sample data
|
||||
- Destroyed when PR is closed
|
||||
- Useful for UI/UX review
|
||||
|
||||
## Tips and Tricks
|
||||
|
||||
### Using Docker for Development
|
||||
|
||||
```bash
|
||||
# Rebuild specific service
|
||||
docker compose build superset
|
||||
|
||||
# View logs
|
||||
docker compose logs -f superset
|
||||
|
||||
# Execute commands in container
|
||||
docker compose exec superset bash
|
||||
|
||||
# Reset database
|
||||
docker compose down -v
|
||||
docker compose up
|
||||
```
|
||||
|
||||
### Hot Reloading
|
||||
|
||||
**Frontend**: Webpack dev server provides hot module replacement automatically.
|
||||
|
||||
**Backend**: Use Flask debug mode:
|
||||
```bash
|
||||
FLASK_ENV=development superset run -p 8088 --with-threads --reload
|
||||
```
|
||||
|
||||
### Performance Profiling
|
||||
|
||||
For Python profiling:
|
||||
```python
|
||||
# In superset_config.py
|
||||
PROFILING = True
|
||||
```
|
||||
|
||||
For React profiling:
|
||||
- Use React DevTools Profiler
|
||||
- Enable performance marks in Chrome DevTools
|
||||
|
||||
### Database Migrations
|
||||
|
||||
```bash
|
||||
# Create a new migration
|
||||
superset db migrate -m "Description of changes"
|
||||
|
||||
# Apply migrations
|
||||
superset db upgrade
|
||||
|
||||
# Downgrade
|
||||
superset db downgrade
|
||||
```
|
||||
|
||||
### Useful Aliases
|
||||
|
||||
Add to your shell profile:
|
||||
|
||||
```bash
|
||||
alias sdev='FLASK_ENV=development superset run -p 8088 --with-threads --reload'
|
||||
alias stest='pytest tests/unit_tests'
|
||||
alias slint='pre-commit run --all-files'
|
||||
alias sfront='cd superset-frontend && npm run dev-server'
|
||||
```
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Node/npm Issues
|
||||
|
||||
```bash
|
||||
# Clear npm cache
|
||||
npm cache clean --force
|
||||
|
||||
# Reinstall dependencies
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
### Python Environment Issues
|
||||
|
||||
```bash
|
||||
# Recreate virtual environment
|
||||
deactivate
|
||||
rm -rf venv
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements/development.txt
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
### Database Issues
|
||||
|
||||
```bash
|
||||
# Reset local database
|
||||
superset db downgrade -r base
|
||||
superset db upgrade
|
||||
superset init
|
||||
```
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
```bash
|
||||
# Find process using port
|
||||
lsof -i :8088
|
||||
# Kill process
|
||||
kill -9 [PID]
|
||||
```
|
||||
|
||||
## Reporting Security Vulnerabilities
|
||||
|
||||
Please report security vulnerabilities to **private@superset.apache.org**.
|
||||
|
||||
In the event a community member discovers a security flaw in Superset, it is important to follow the [Apache Security Guidelines](https://www.apache.org/security/committers.html) and release a fix as quickly as possible before public disclosure. Reporting security vulnerabilities through the usual GitHub Issues channel is not ideal as it will publicize the flaw before a fix can be applied.
|
||||
|
||||
## SQL Lab Async Configuration
|
||||
|
||||
It's possible to configure a local database to operate in `async` mode, to work on `async` related features.
|
||||
|
||||
To do this, you'll need to:
|
||||
|
||||
- Add an additional database entry. We recommend you copy the connection string from the database labeled `main`, and then enable `SQL Lab` and the features you want to use. Don't forget to check the `Async` box
|
||||
- Configure a results backend, here's a local `FileSystemCache` example, not recommended for production, but perfect for testing (stores cache in `/tmp`)
|
||||
|
||||
```python
|
||||
from flask_caching.backends.filesystemcache import FileSystemCache
|
||||
RESULTS_BACKEND = FileSystemCache('/tmp/sqllab')
|
||||
```
|
||||
|
||||
- Start up a celery worker
|
||||
|
||||
```bash
|
||||
celery --app=superset.tasks.celery_app:app worker -O fair
|
||||
```
|
||||
|
||||
Note that:
|
||||
- for changes that affect the worker logic, you'll have to restart the `celery worker` process for the changes to be reflected.
|
||||
- The message queue used is a `sqlite` database using the `SQLAlchemy` experimental broker. Ok for testing, but not recommended in production
|
||||
- In some cases, you may want to create a context that is more aligned to your production environment, and use the similar broker as well as results backend configuration
|
||||
|
||||
## Async Chart Queries
|
||||
|
||||
It's possible to configure database queries for charts to operate in `async` mode. This is especially useful for dashboards with many charts that may otherwise be affected by browser connection limits. To enable async queries for dashboards and Explore, the following dependencies are required:
|
||||
|
||||
- Redis 5.0+ (the feature utilizes [Redis Streams](https://redis.io/topics/streams-intro))
|
||||
- Cache backends enabled via the `CACHE_CONFIG` and `DATA_CACHE_CONFIG` config settings
|
||||
- Celery workers configured and running to process async tasks
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Check the [FAQ](https://superset.apache.org/docs/frequently-asked-questions)
|
||||
- Ask in [Slack](https://apache-superset.slack.com)
|
||||
- Search [GitHub Issues](https://github.com/apache/superset/issues)
|
||||
- Post in [GitHub Discussions](https://github.com/apache/superset/discussions)
|
||||
418
docs/developer_portal/contributing/issue-reporting.md
Normal file
418
docs/developer_portal/contributing/issue-reporting.md
Normal file
@@ -0,0 +1,418 @@
|
||||
---
|
||||
title: Issue Reporting
|
||||
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.
|
||||
-->
|
||||
|
||||
# Issue Reporting
|
||||
|
||||
Learn how to effectively report bugs and request features for Apache Superset.
|
||||
|
||||
## Before Opening an Issue
|
||||
|
||||
### Pre-Issue Checklist
|
||||
|
||||
1. **Search Existing Issues**
|
||||
```
|
||||
Search: https://github.com/apache/superset/issues
|
||||
- Use keywords from your error message
|
||||
- Check both open and closed issues
|
||||
- Look for similar problems
|
||||
```
|
||||
|
||||
2. **Check Documentation**
|
||||
- [User Documentation](https://superset.apache.org/docs/intro)
|
||||
- [FAQ](https://superset.apache.org/docs/frequently-asked-questions)
|
||||
- [Configuration Guide](https://superset.apache.org/docs/configuration/configuring-superset)
|
||||
|
||||
3. **Verify Version**
|
||||
```bash
|
||||
# Check Superset version
|
||||
superset version
|
||||
|
||||
# Try latest version
|
||||
pip install --upgrade apache-superset
|
||||
```
|
||||
|
||||
4. **Reproduce Consistently**
|
||||
- Can you reproduce the issue?
|
||||
- Does it happen every time?
|
||||
- What specific actions trigger it?
|
||||
|
||||
## Bug Reports
|
||||
|
||||
### Bug Report Template
|
||||
|
||||
```markdown
|
||||
### Bug Description
|
||||
A clear and concise description of the bug.
|
||||
|
||||
### How to Reproduce
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
|
||||
### Expected Behavior
|
||||
What you expected to happen.
|
||||
|
||||
### Actual Behavior
|
||||
What actually happened. Include error messages.
|
||||
|
||||
### Screenshots/Videos
|
||||
If applicable, add screenshots or recordings.
|
||||
|
||||
### Environment
|
||||
- Superset version: [e.g., 3.0.0]
|
||||
- Python version: [e.g., 3.9.7]
|
||||
- Node version: [e.g., 18.17.0]
|
||||
- Database: [e.g., PostgreSQL 14]
|
||||
- Browser: [e.g., Chrome 120]
|
||||
- OS: [e.g., Ubuntu 22.04]
|
||||
|
||||
### Additional Context
|
||||
- Using Docker: Yes/No
|
||||
- Configuration overrides:
|
||||
- Feature flags enabled:
|
||||
- Authentication method:
|
||||
```
|
||||
|
||||
### What Makes a Good Bug Report
|
||||
|
||||
#### ✅ Good Example
|
||||
```markdown
|
||||
### Bug Description
|
||||
When filtering a dashboard with a date range filter, charts using
|
||||
SQL Lab datasets don't update, while charts using regular datasets do.
|
||||
|
||||
### How to Reproduce
|
||||
1. Create a dashboard with 2 charts:
|
||||
- Chart A: Uses a SQL Lab virtual dataset
|
||||
- Chart B: Uses a regular table dataset
|
||||
2. Add a date range filter (last 30 days)
|
||||
3. Apply the filter
|
||||
4. Chart B updates, Chart A shows no change
|
||||
|
||||
### Expected Behavior
|
||||
Both charts should filter to show last 30 days of data.
|
||||
|
||||
### Actual Behavior
|
||||
Only Chart B updates. Chart A still shows all data.
|
||||
No error messages in browser console or server logs.
|
||||
|
||||
### Screenshots
|
||||
[Dashboard before filter]: attachment1.png
|
||||
[Dashboard after filter]: attachment2.png
|
||||
[Network tab showing requests]: attachment3.png
|
||||
|
||||
### Environment
|
||||
- Superset version: 3.0.0
|
||||
- Python version: 3.9.16
|
||||
- Database: PostgreSQL 14.9
|
||||
- Browser: Chrome 120.0.6099.71
|
||||
- OS: macOS 14.2
|
||||
```
|
||||
|
||||
#### ❌ Poor Example
|
||||
```markdown
|
||||
Dashboard filters don't work. Please fix.
|
||||
```
|
||||
|
||||
### Required Information
|
||||
|
||||
#### Error Messages
|
||||
```python
|
||||
# Include full error traceback
|
||||
Traceback (most recent call last):
|
||||
File "...", line X, in function
|
||||
error details
|
||||
SupersetException: Detailed error message
|
||||
```
|
||||
|
||||
#### Logs
|
||||
```bash
|
||||
# Backend logs
|
||||
docker logs superset_app 2>&1 | tail -100
|
||||
|
||||
# Or from development
|
||||
tail -f ~/.superset/superset.log
|
||||
```
|
||||
|
||||
#### Browser Console
|
||||
```javascript
|
||||
// Include JavaScript errors
|
||||
// Chrome: F12 → Console tab
|
||||
// Include network errors
|
||||
// Chrome: F12 → Network tab
|
||||
```
|
||||
|
||||
#### Configuration
|
||||
```python
|
||||
# Relevant config from superset_config.py
|
||||
FEATURE_FLAGS = {
|
||||
"ENABLE_TEMPLATE_PROCESSING": True,
|
||||
# ... other flags
|
||||
}
|
||||
```
|
||||
|
||||
## Feature Requests
|
||||
|
||||
### Feature Request Template
|
||||
|
||||
```markdown
|
||||
### Is your feature request related to a problem?
|
||||
A clear description of the problem you're trying to solve.
|
||||
|
||||
### Describe the solution you'd like
|
||||
A clear description of what you want to happen.
|
||||
|
||||
### Describe alternatives you've considered
|
||||
Other solutions or features you've considered.
|
||||
|
||||
### Additional context
|
||||
Any other context, mockups, or examples.
|
||||
|
||||
### Are you willing to contribute?
|
||||
- [ ] Yes, I can implement this feature
|
||||
- [ ] Yes, I can help test
|
||||
- [ ] No, but I can provide feedback
|
||||
```
|
||||
|
||||
### Good Feature Requests Include
|
||||
|
||||
1. **Clear Use Case**
|
||||
```markdown
|
||||
As a [type of user], I want [feature] so that [benefit].
|
||||
|
||||
Example:
|
||||
As a data analyst, I want to schedule dashboard screenshots
|
||||
so that I can automatically send reports to stakeholders.
|
||||
```
|
||||
|
||||
2. **Mockups/Designs**
|
||||
- UI mockups
|
||||
- Workflow diagrams
|
||||
- API specifications
|
||||
|
||||
3. **Impact Analysis**
|
||||
- Who benefits?
|
||||
- How many users affected?
|
||||
- Performance implications?
|
||||
|
||||
## Security Issues
|
||||
|
||||
### 🔴 IMPORTANT: Security Vulnerabilities
|
||||
|
||||
**DO NOT** create public issues for security vulnerabilities!
|
||||
|
||||
Instead:
|
||||
1. Email: security@apache.org
|
||||
2. Subject: `[Superset] Security Vulnerability`
|
||||
3. Include:
|
||||
- Description of vulnerability
|
||||
- Steps to reproduce
|
||||
- Potential impact
|
||||
- Suggested fix (if any)
|
||||
|
||||
### Security Issue Template
|
||||
|
||||
```markdown
|
||||
Send to: security@apache.org
|
||||
|
||||
### Vulnerability Description
|
||||
[Describe the security issue]
|
||||
|
||||
### Type
|
||||
- [ ] SQL Injection
|
||||
- [ ] XSS
|
||||
- [ ] CSRF
|
||||
- [ ] Authentication Bypass
|
||||
- [ ] Information Disclosure
|
||||
- [ ] Other: [specify]
|
||||
|
||||
### Affected Versions
|
||||
[List affected versions]
|
||||
|
||||
### Steps to Reproduce
|
||||
[Detailed steps - be specific]
|
||||
|
||||
### Impact
|
||||
[What can an attacker do?]
|
||||
|
||||
### Suggested Fix
|
||||
[If you have suggestions]
|
||||
```
|
||||
|
||||
## Issue Labels
|
||||
|
||||
### Priority Labels
|
||||
- `P0`: Critical - System unusable
|
||||
- `P1`: High - Major feature broken
|
||||
- `P2`: Medium - Important but workaround exists
|
||||
- `P3`: Low - Nice to have
|
||||
|
||||
### Type Labels
|
||||
- `bug`: Something isn't working
|
||||
- `feature`: New feature request
|
||||
- `enhancement`: Improvement to existing feature
|
||||
- `documentation`: Documentation improvements
|
||||
- `question`: Question about usage
|
||||
|
||||
### Component Labels
|
||||
- `dashboard`: Dashboard functionality
|
||||
- `sqllab`: SQL Lab
|
||||
- `explore`: Chart builder
|
||||
- `visualization`: Chart types
|
||||
- `api`: REST API
|
||||
- `security`: Security related
|
||||
|
||||
### Status Labels
|
||||
- `needs-triage`: Awaiting review
|
||||
- `confirmed`: Bug confirmed
|
||||
- `in-progress`: Being worked on
|
||||
- `blocked`: Blocked by dependency
|
||||
- `stale`: No activity for 30+ days
|
||||
|
||||
## Issue Lifecycle
|
||||
|
||||
### 1. Creation
|
||||
- User creates issue with template
|
||||
- Auto-labeled as `needs-triage`
|
||||
|
||||
### 2. Triage
|
||||
- Maintainer reviews within 7 days
|
||||
- Labels applied (priority, type, component)
|
||||
- Questions asked if needed
|
||||
|
||||
### 3. Confirmation
|
||||
- Bug reproduced or feature discussed
|
||||
- Label changed to `confirmed`
|
||||
- Assigned to milestone if applicable
|
||||
|
||||
### 4. Development
|
||||
- Contributor claims issue
|
||||
- Label changed to `in-progress`
|
||||
- PR linked to issue
|
||||
|
||||
### 5. Resolution
|
||||
- PR merged
|
||||
- Issue auto-closed
|
||||
- Or manually closed with explanation
|
||||
|
||||
## Following Up
|
||||
|
||||
### If No Response
|
||||
|
||||
After 7 days without response:
|
||||
```markdown
|
||||
@apache/superset-committers This issue hasn't been triaged yet.
|
||||
Could someone please take a look?
|
||||
```
|
||||
|
||||
### Providing Updates
|
||||
|
||||
```markdown
|
||||
Update: I found that this only happens when [condition].
|
||||
Here's additional debugging information: [details]
|
||||
```
|
||||
|
||||
### Issue Staleness
|
||||
|
||||
- Bot marks stale after 30 days of inactivity
|
||||
- Closes after 7 more days without activity
|
||||
- To keep open: Comment with updates
|
||||
|
||||
## Tips for Success
|
||||
|
||||
### Do's
|
||||
- ✅ Search before creating
|
||||
- ✅ Use templates
|
||||
- ✅ Provide complete information
|
||||
- ✅ Include screenshots/videos
|
||||
- ✅ Be responsive to questions
|
||||
- ✅ Test with latest version
|
||||
- ✅ One issue per report
|
||||
|
||||
### Don'ts
|
||||
- ❌ "+1" or "me too" comments (use reactions)
|
||||
- ❌ Multiple issues in one report
|
||||
- ❌ Vague descriptions
|
||||
- ❌ Screenshots of text (copy/paste instead)
|
||||
- ❌ Private/sensitive data in reports
|
||||
- ❌ Demanding immediate fixes
|
||||
|
||||
## Useful Commands
|
||||
|
||||
### Gathering System Info
|
||||
|
||||
```bash
|
||||
# Full environment info
|
||||
python -c "
|
||||
import sys
|
||||
import superset
|
||||
import sqlalchemy
|
||||
import pandas
|
||||
import numpy
|
||||
|
||||
print(f'Python: {sys.version}')
|
||||
print(f'Superset: {superset.__version__}')
|
||||
print(f'SQLAlchemy: {sqlalchemy.__version__}')
|
||||
print(f'Pandas: {pandas.__version__}')
|
||||
print(f'NumPy: {numpy.__version__}')
|
||||
"
|
||||
|
||||
# Database versions
|
||||
superset shell
|
||||
>>> from superset import db
|
||||
>>> print(db.engine.dialect.server_version_info)
|
||||
```
|
||||
|
||||
### Creating Minimal Reproductions
|
||||
|
||||
```python
|
||||
# Create test script
|
||||
# minimal_repro.py
|
||||
from superset import create_app
|
||||
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
# Your reproduction code here
|
||||
pass
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
### Before Creating an Issue
|
||||
|
||||
1. **Slack**: Ask in #troubleshooting
|
||||
2. **GitHub Discussions**: Search/ask questions
|
||||
3. **Stack Overflow**: Tag `apache-superset`
|
||||
4. **Mailing List**: user@superset.apache.org
|
||||
|
||||
### Issue Not a Bug?
|
||||
|
||||
Consider:
|
||||
- **Feature Request**: Use feature request template
|
||||
- **Question**: Use GitHub Discussions
|
||||
- **Configuration Help**: Ask in Slack
|
||||
- **Development Help**: See [Contributing Guide](./overview)
|
||||
|
||||
Next: [Understanding the release process](./release-process)
|
||||
166
docs/developer_portal/contributing/overview.md
Normal file
166
docs/developer_portal/contributing/overview.md
Normal file
@@ -0,0 +1,166 @@
|
||||
---
|
||||
title: 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.
|
||||
-->
|
||||
|
||||
# Contributing
|
||||
|
||||
Superset is an [Apache Software foundation](https://www.apache.org/theapacheway/index.html) project.
|
||||
The core contributors (or committers) to Superset communicate primarily in the following channels (which can be joined by anyone):
|
||||
|
||||
- [Mailing list](https://lists.apache.org/list.html?dev@superset.apache.org)
|
||||
- [Apache Superset Slack community](http://bit.ly/join-superset-slack)
|
||||
- [GitHub issues](https://github.com/apache/superset/issues)
|
||||
- [GitHub pull requests](https://github.com/apache/superset/pulls)
|
||||
- [GitHub discussions](https://github.com/apache/superset/discussions)
|
||||
- [Superset Community Calendar](https://superset.apache.org/community)
|
||||
|
||||
More references:
|
||||
|
||||
- [Superset Wiki (code guidelines and additional resources)](https://github.com/apache/superset/wiki)
|
||||
|
||||
## Orientation
|
||||
|
||||
Here's a list of repositories that contain Superset-related packages:
|
||||
|
||||
- [apache/superset](https://github.com/apache/superset)
|
||||
is the main repository containing the `apache_superset` Python package
|
||||
distributed on
|
||||
[pypi](https://pypi.org/project/apache_superset/). This repository
|
||||
also includes Superset's main TypeScript/JavaScript bundles and react apps under
|
||||
the [superset-frontend](https://github.com/apache/superset/tree/master/superset-frontend)
|
||||
folder.
|
||||
- [github.com/apache-superset](https://github.com/apache-superset) is the
|
||||
GitHub organization under which we manage Superset-related
|
||||
small tools, forks and Superset-related experimental ideas.
|
||||
|
||||
## Types of Contributions
|
||||
|
||||
### Report Bug
|
||||
|
||||
The best way to report a bug is to file an issue on GitHub. Please include:
|
||||
|
||||
- Your operating system name and version.
|
||||
- Superset version.
|
||||
- Detailed steps to reproduce the bug.
|
||||
- Any details about your local setup that might be helpful in troubleshooting.
|
||||
|
||||
When posting Python stack traces, please quote them using
|
||||
[Markdown blocks](https://help.github.com/articles/creating-and-highlighting-code-blocks/).
|
||||
|
||||
_Please note that feature requests opened as GitHub Issues will be moved to Discussions._
|
||||
|
||||
### Submit Ideas or Feature Requests
|
||||
|
||||
The best way is to start an ["Ideas" Discussion thread](https://github.com/apache/superset/discussions/categories/ideas) on GitHub:
|
||||
|
||||
- Explain in detail how it would work.
|
||||
- Keep the scope as narrow as possible, to make it easier to implement.
|
||||
- Remember that this is a volunteer-driven project, and that your contributions are as welcome as anyone's :)
|
||||
|
||||
To propose large features or major changes to codebase, and help usher in those changes, please create a **Superset Improvement Proposal (SIP)**. See template from [SIP-0](https://github.com/apache/superset/issues/5602)
|
||||
|
||||
### Fix Bugs
|
||||
|
||||
Look through the GitHub issues. Issues tagged with `#bug` are
|
||||
open to whoever wants to implement them.
|
||||
|
||||
### Implement Features
|
||||
|
||||
Look through the GitHub issues. Issues tagged with
|
||||
`#feature` are open to whoever wants to implement them.
|
||||
|
||||
### Improve Documentation
|
||||
|
||||
Superset could always use better documentation,
|
||||
whether as part of the official Superset docs,
|
||||
in docstrings, `docs/*.rst` or even on the web as blog posts or
|
||||
articles. See [Documentation](./howtos#contributing-to-documentation) for more details.
|
||||
|
||||
### Add Translations
|
||||
|
||||
If you are proficient in a non-English language, you can help translate
|
||||
text strings from Superset's UI. You can jump into the existing
|
||||
language dictionaries at
|
||||
`superset/translations/<language_code>/LC_MESSAGES/messages.po`, or
|
||||
even create a dictionary for a new language altogether.
|
||||
See [Translating](./howtos#contributing-translations) for more details.
|
||||
|
||||
### Ask Questions
|
||||
|
||||
There is a dedicated [`apache-superset` tag](https://stackoverflow.com/questions/tagged/apache-superset) on [StackOverflow](https://stackoverflow.com/). Please use it when asking questions.
|
||||
|
||||
## Types of Contributors
|
||||
|
||||
Following the project governance model of the Apache Software Foundation (ASF), Apache Superset has a specific set of contributor roles:
|
||||
|
||||
### PMC Member
|
||||
|
||||
A Project Management Committee (PMC) member is a person who has been elected by the PMC to help manage the project. PMC members are responsible for the overall health of the project, including community development, release management, and project governance. PMC members are also responsible for the technical direction of the project.
|
||||
|
||||
For more information about Apache Project PMCs, please refer to https://www.apache.org/foundation/governance/pmcs.html
|
||||
|
||||
### Committer
|
||||
|
||||
A committer is a person who has been elected by the PMC to have write access (commit access) to the code repository. They can modify the code, documentation, and website and accept contributions from others.
|
||||
|
||||
The official list of committers and PMC members can be found [here](https://projects.apache.org/committee.html?superset).
|
||||
|
||||
### Contributor
|
||||
|
||||
A contributor is a person who has contributed to the project in any way, including but not limited to code, tests, documentation, issues, and discussions.
|
||||
|
||||
> You can also review the Superset project's guidelines for PMC member promotion here: https://github.com/apache/superset/wiki/Guidelines-for-promoting-Superset-Committers-to-the-Superset-PMC
|
||||
|
||||
### Security Team
|
||||
|
||||
The security team is a selected subset of PMC members, committers and non-committers who are responsible for handling security issues.
|
||||
|
||||
New members of the security team are selected by the PMC members in a vote. You can request to be added to the team by sending a message to private@superset.apache.org. However, the team should be small and focused on solving security issues, so the requests will be evaluated on a case-by-case basis and the team size will be kept relatively small, limited to only actively security-focused contributors.
|
||||
|
||||
This security team must follow the [ASF vulnerability handling process](https://apache.org/security/committers.html#asf-project-security-for-committers).
|
||||
|
||||
Each new security issue is tracked as a JIRA ticket on the [ASF's JIRA Superset security project](https://issues.apache.org/jira/secure/RapidBoard.jspa?rapidView=588&projectKey=SUPERSETSEC)
|
||||
|
||||
Security team members must:
|
||||
|
||||
- Have an [ICLA](https://www.apache.org/licenses/contributor-agreements.html) signed with Apache Software Foundation.
|
||||
- Not reveal information about pending and unfixed security issues to anyone (including their employers) unless specifically authorised by the security team members, e.g., if the security team agrees that diagnosing and solving an issue requires the involvement of external experts.
|
||||
|
||||
A release manager, the contributor overseeing the release of a specific version of Apache Superset, is by default a member of the security team. However, they are not expected to be active in assessing, discussing, and fixing security issues.
|
||||
|
||||
Security team members should also follow these general expectations:
|
||||
|
||||
- Actively participate in assessing, discussing, fixing, and releasing security issues in Superset.
|
||||
- Avoid discussing security fixes in public forums. Pull request (PR) descriptions should not contain any information about security issues. The corresponding JIRA ticket should contain a link to the PR.
|
||||
- Security team members who contribute to a fix may be listed as remediation developers in the CVE report, along with their job affiliation (if they choose to include it).
|
||||
|
||||
## Getting Started
|
||||
|
||||
Ready to contribute? Here's how to get started:
|
||||
|
||||
1. **[Set up your environment](./development-setup)** - Get Superset running locally
|
||||
2. **[Find something to work on](#types-of-contributions)** - Pick an issue or feature
|
||||
3. **[Submit your contribution](./submitting-pr)** - Create a pull request
|
||||
4. **[Follow guidelines](./guidelines)** - Ensure code quality
|
||||
|
||||
Welcome to the Apache Superset community! 🚀
|
||||
469
docs/developer_portal/contributing/release-process.md
Normal file
469
docs/developer_portal/contributing/release-process.md
Normal file
@@ -0,0 +1,469 @@
|
||||
---
|
||||
title: Release Process
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Release Process
|
||||
|
||||
Understand Apache Superset's release process, versioning strategy, and how to participate.
|
||||
|
||||
## Release Cadence
|
||||
|
||||
### Schedule
|
||||
- **Major releases (X.0.0)**: Annually (approximately)
|
||||
- **Minor releases (X.Y.0)**: Quarterly
|
||||
- **Patch releases (X.Y.Z)**: As needed for critical fixes
|
||||
|
||||
### Version Numbering
|
||||
|
||||
Superset follows [Semantic Versioning](https://semver.org/):
|
||||
|
||||
```
|
||||
MAJOR.MINOR.PATCH
|
||||
↓ ↓ ↓
|
||||
│ │ └── Bug fixes, security patches
|
||||
│ └────── New features, backwards compatible
|
||||
└──────────── Breaking changes
|
||||
```
|
||||
|
||||
### Examples
|
||||
- `3.0.0`: Major release with breaking changes
|
||||
- `3.1.0`: Minor release with new features
|
||||
- `3.1.1`: Patch release with bug fixes
|
||||
|
||||
## Release Types
|
||||
|
||||
### Major Releases (X.0.0)
|
||||
|
||||
#### Includes
|
||||
- Breaking API changes
|
||||
- Deprecated feature removals
|
||||
- Major architectural changes
|
||||
- Database migration requirements
|
||||
|
||||
#### Process
|
||||
- 2-3 month preparation period
|
||||
- Multiple release candidates (RC)
|
||||
- Extensive testing period
|
||||
- Migration guides required
|
||||
|
||||
### Minor Releases (X.Y.0)
|
||||
|
||||
#### Includes
|
||||
- New features
|
||||
- Performance improvements
|
||||
- Non-breaking API additions
|
||||
- Minor UI/UX updates
|
||||
|
||||
#### Process
|
||||
- 1 month preparation
|
||||
- 1-2 release candidates
|
||||
- Standard testing period
|
||||
|
||||
### Patch Releases (X.Y.Z)
|
||||
|
||||
#### Includes
|
||||
- Bug fixes
|
||||
- Security patches
|
||||
- Documentation fixes
|
||||
- Dependency updates (security)
|
||||
|
||||
#### Process
|
||||
- Fast track for critical issues
|
||||
- May skip RC for urgent security fixes
|
||||
- Minimal testing requirements
|
||||
|
||||
## Release Process (For Release Managers)
|
||||
|
||||
### 1. Pre-Release Preparation
|
||||
|
||||
#### Feature Freeze
|
||||
```bash
|
||||
# Create release branch
|
||||
git checkout -b release-X.Y
|
||||
git push upstream release-X.Y
|
||||
|
||||
# Update version
|
||||
# Edit setup.py and package.json
|
||||
VERSION = "X.Y.0rc1"
|
||||
```
|
||||
|
||||
#### Update Documentation
|
||||
- CHANGELOG.md
|
||||
- UPDATING.md (for breaking changes)
|
||||
- Documentation version
|
||||
|
||||
#### Release Notes Template
|
||||
```markdown
|
||||
# Apache Superset X.Y.0
|
||||
|
||||
## 🎉 Highlights
|
||||
- Major feature 1
|
||||
- Major feature 2
|
||||
|
||||
## 🚀 New Features
|
||||
- Feature 1 (#PR)
|
||||
- Feature 2 (#PR)
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
- Fix 1 (#PR)
|
||||
- Fix 2 (#PR)
|
||||
|
||||
## ⚠️ Breaking Changes
|
||||
- Breaking change 1
|
||||
- Migration required for X
|
||||
|
||||
## 📝 Documentation
|
||||
- Doc update 1 (#PR)
|
||||
|
||||
## 🙏 Thank You
|
||||
Thanks to all contributors!
|
||||
```
|
||||
|
||||
### 2. Create Release Candidate
|
||||
|
||||
#### Build RC
|
||||
```bash
|
||||
# Tag release candidate
|
||||
git tag -a vX.Y.Zrc1 -m "Apache Superset X.Y.Z RC1"
|
||||
git push upstream vX.Y.Zrc1
|
||||
|
||||
# Build source distribution
|
||||
python setup.py sdist
|
||||
|
||||
# Build wheel
|
||||
python setup.py bdist_wheel
|
||||
|
||||
# Sign artifacts
|
||||
gpg --armor --detach-sig dist/apache-superset-X.Y.Zrc1.tar.gz
|
||||
```
|
||||
|
||||
#### Upload to staging
|
||||
```bash
|
||||
# Upload to Apache staging
|
||||
svn co https://dist.apache.org/repos/dist/dev/superset
|
||||
cd superset
|
||||
mkdir X.Y.Zrc1
|
||||
cp /path/to/dist/* X.Y.Zrc1/
|
||||
svn add X.Y.Zrc1
|
||||
svn commit -m "Add Apache Superset X.Y.Z RC1"
|
||||
```
|
||||
|
||||
### 3. Voting Process
|
||||
|
||||
#### Call for Vote Email
|
||||
|
||||
Send to dev@superset.apache.org:
|
||||
|
||||
```
|
||||
Subject: [VOTE] Release Apache Superset X.Y.Z RC1
|
||||
|
||||
Hi all,
|
||||
|
||||
I'd like to call a vote to release Apache Superset version X.Y.Z RC1.
|
||||
|
||||
The release candidate:
|
||||
- Git tag: vX.Y.Zrc1
|
||||
- Git hash: abc123def456
|
||||
- Source: https://dist.apache.org/repos/dist/dev/superset/X.Y.Zrc1/
|
||||
|
||||
Resources:
|
||||
- Release notes: [link]
|
||||
- CHANGELOG: [link]
|
||||
- PR list: [link]
|
||||
|
||||
The vote will be open for at least 72 hours.
|
||||
|
||||
[ ] +1 approve
|
||||
[ ] +0 no opinion
|
||||
[ ] -1 disapprove (and reason why)
|
||||
|
||||
Thanks,
|
||||
[Your name]
|
||||
```
|
||||
|
||||
#### Voting Rules
|
||||
- **Duration**: Minimum 72 hours
|
||||
- **Required**: 3 +1 votes from PMC members
|
||||
- **Veto**: Any -1 vote must be addressed
|
||||
|
||||
#### Testing Checklist
|
||||
```markdown
|
||||
- [ ] Source builds successfully
|
||||
- [ ] Docker image builds
|
||||
- [ ] Basic functionality works
|
||||
- [ ] No critical bugs
|
||||
- [ ] License check passes
|
||||
- [ ] Security scan clean
|
||||
```
|
||||
|
||||
### 4. Release Approval
|
||||
|
||||
#### Tally Votes
|
||||
```
|
||||
Subject: [RESULT][VOTE] Release Apache Superset X.Y.Z RC1
|
||||
|
||||
The vote to release Apache Superset X.Y.Z RC1 has passed.
|
||||
|
||||
+1 votes (binding):
|
||||
- PMC Member 1
|
||||
- PMC Member 2
|
||||
- PMC Member 3
|
||||
|
||||
+1 votes (non-binding):
|
||||
- Contributor 1
|
||||
- Contributor 2
|
||||
|
||||
0 votes:
|
||||
- None
|
||||
|
||||
-1 votes:
|
||||
- None
|
||||
|
||||
Thank you to everyone who tested and voted!
|
||||
```
|
||||
|
||||
### 5. Perform Release
|
||||
|
||||
#### Promote RC to Release
|
||||
```bash
|
||||
# Tag final release
|
||||
git tag -a vX.Y.Z -m "Apache Superset X.Y.Z"
|
||||
git push upstream vX.Y.Z
|
||||
|
||||
# Move from dev to release
|
||||
svn mv https://dist.apache.org/repos/dist/dev/superset/X.Y.Zrc1 \
|
||||
https://dist.apache.org/repos/dist/release/superset/X.Y.Z
|
||||
```
|
||||
|
||||
#### Publish to PyPI
|
||||
```bash
|
||||
# Upload to PyPI
|
||||
python -m twine upload dist/*X.Y.Z*
|
||||
```
|
||||
|
||||
#### Build Docker Images
|
||||
```bash
|
||||
# Build and push Docker images
|
||||
docker build -t apache/superset:X.Y.Z .
|
||||
docker push apache/superset:X.Y.Z
|
||||
docker tag apache/superset:X.Y.Z apache/superset:latest
|
||||
docker push apache/superset:latest
|
||||
```
|
||||
|
||||
### 6. Post-Release Tasks
|
||||
|
||||
#### Update Documentation
|
||||
```bash
|
||||
# Update docs version
|
||||
cd docs
|
||||
# Update docusaurus.config.js with new version
|
||||
npm run build
|
||||
```
|
||||
|
||||
#### Announcement Email
|
||||
|
||||
Send to announce@apache.org, dev@superset.apache.org:
|
||||
|
||||
```
|
||||
Subject: [ANNOUNCE] Apache Superset X.Y.Z Released
|
||||
|
||||
The Apache Superset team is pleased to announce the release of
|
||||
Apache Superset X.Y.Z.
|
||||
|
||||
Apache Superset is a modern data exploration and visualization platform.
|
||||
|
||||
This release includes [number] commits from [number] contributors.
|
||||
|
||||
Highlights:
|
||||
- Feature 1
|
||||
- Feature 2
|
||||
- Bug fixes and improvements
|
||||
|
||||
Download: https://superset.apache.org/docs/installation/
|
||||
Release Notes: https://github.com/apache/superset/releases/tag/vX.Y.Z
|
||||
PyPI: https://pypi.org/project/apache-superset/
|
||||
Docker: docker pull apache/superset:X.Y.Z
|
||||
|
||||
Thanks to all contributors who made this release possible!
|
||||
|
||||
The Apache Superset Team
|
||||
```
|
||||
|
||||
#### Update GitHub Release
|
||||
```bash
|
||||
# Create GitHub release
|
||||
gh release create vX.Y.Z \
|
||||
--title "Apache Superset X.Y.Z" \
|
||||
--notes-file RELEASE_NOTES.md
|
||||
```
|
||||
|
||||
## For Contributors
|
||||
|
||||
### During Feature Freeze
|
||||
|
||||
#### What's Allowed
|
||||
- ✅ Bug fixes
|
||||
- ✅ Documentation updates
|
||||
- ✅ Test improvements
|
||||
- ✅ Security fixes
|
||||
|
||||
#### What's Not Allowed
|
||||
- ❌ New features
|
||||
- ❌ Major refactoring
|
||||
- ❌ Breaking changes
|
||||
- ❌ Risky changes
|
||||
|
||||
### Testing RCs
|
||||
|
||||
#### How to Test
|
||||
```bash
|
||||
# Install RC from staging
|
||||
pip install https://dist.apache.org/repos/dist/dev/superset/X.Y.Zrc1/apache-superset-X.Y.Zrc1.tar.gz
|
||||
|
||||
# Or use Docker
|
||||
docker pull apache/superset:X.Y.Zrc1
|
||||
```
|
||||
|
||||
#### What to Test
|
||||
- Your use cases
|
||||
- New features mentioned in release notes
|
||||
- Upgrade from previous version
|
||||
- Database migrations
|
||||
- Critical workflows
|
||||
|
||||
#### Reporting Issues
|
||||
```markdown
|
||||
Found issue in RC1:
|
||||
- Description: [what's wrong]
|
||||
- Steps to reproduce: [how to trigger]
|
||||
- Impact: [blocker/major/minor]
|
||||
- Suggested fix: [if known]
|
||||
```
|
||||
|
||||
### CHANGELOG Maintenance
|
||||
|
||||
#### Format
|
||||
```markdown
|
||||
## X.Y.Z (YYYY-MM-DD)
|
||||
|
||||
### Features
|
||||
- feat: Description (#PR_NUMBER)
|
||||
|
||||
### Fixes
|
||||
- fix: Description (#PR_NUMBER)
|
||||
|
||||
### Breaking Changes
|
||||
- BREAKING: Description (#PR_NUMBER)
|
||||
Migration: Steps to migrate
|
||||
```
|
||||
|
||||
#### Generating CHANGELOG
|
||||
```bash
|
||||
# Use git log to generate initial list
|
||||
git log --oneline vX.Y-1.Z..vX.Y.Z | grep -E "^[a-f0-9]+ (feat|fix|perf|refactor|docs)"
|
||||
|
||||
# Group by type and format
|
||||
```
|
||||
|
||||
## Breaking Changes Process
|
||||
|
||||
### Documentation Required
|
||||
|
||||
#### UPDATING.md Entry
|
||||
```markdown
|
||||
# X.Y.Z
|
||||
|
||||
## Breaking Change: [Title]
|
||||
|
||||
### Description
|
||||
What changed and why.
|
||||
|
||||
### Before
|
||||
```python
|
||||
# Old way
|
||||
old_function(param1, param2)
|
||||
```
|
||||
|
||||
### After
|
||||
```python
|
||||
# New way
|
||||
new_function(param1, param2, param3)
|
||||
```
|
||||
|
||||
### Migration Steps
|
||||
1. Update your code to...
|
||||
2. Run migration script...
|
||||
3. Test that...
|
||||
```
|
||||
|
||||
### Deprecation Process
|
||||
|
||||
1. **Version N**: Mark as deprecated
|
||||
```python
|
||||
@deprecated(version="3.0.0", remove_in="4.0.0")
|
||||
def old_function():
|
||||
warnings.warn("Use new_function instead", DeprecationWarning)
|
||||
```
|
||||
|
||||
2. **Version N+1**: Keep deprecated with warnings
|
||||
|
||||
3. **Version N+2**: Remove completely
|
||||
|
||||
## Security Releases
|
||||
|
||||
### Expedited Process
|
||||
- No RC required for critical security fixes
|
||||
- Coordinate with security@apache.org
|
||||
- Embargo period may apply
|
||||
- CVE assignment through ASF security team
|
||||
|
||||
### Security Advisory Template
|
||||
```markdown
|
||||
CVE-YYYY-XXXXX: [Title]
|
||||
|
||||
Severity: [Critical/High/Medium/Low]
|
||||
Affected Versions: < X.Y.Z
|
||||
Fixed Version: X.Y.Z
|
||||
|
||||
Description:
|
||||
[Vulnerability description]
|
||||
|
||||
Mitigation:
|
||||
[How to fix or work around]
|
||||
|
||||
Credit:
|
||||
[Reporter name/organization]
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
### Internal
|
||||
- [Apache Release Policy](https://www.apache.org/legal/release-policy.html)
|
||||
- [Superset Release History](https://github.com/apache/superset/releases)
|
||||
- [Version Strategy Discussion](https://github.com/apache/superset/discussions)
|
||||
|
||||
### Tools
|
||||
- [Release Scripts](https://github.com/apache/superset/tree/master/scripts/release)
|
||||
- [Superset Repository Scripts](https://github.com/apache/superset/tree/master/scripts)
|
||||
|
||||
Next: Return to [Contributing Overview](./overview)
|
||||
133
docs/developer_portal/contributing/resources.md
Normal file
133
docs/developer_portal/contributing/resources.md
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
title: Resources
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Resources
|
||||
|
||||
## High Level Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
|
||||
%% Top Level
|
||||
LB["<b>Load Balancer(s)</b><br/>(optional)"]
|
||||
LB -.-> WebServers
|
||||
|
||||
%% Web Servers
|
||||
subgraph WebServers ["<b>Web Server(s)</b>"]
|
||||
WS1["<b>Frontend</b><br/>(React, AntD, ECharts, AGGrid)"]
|
||||
WS2["<b>Backend</b><br/>(Python, Flask, SQLAlchemy, Pandas, ...)"]
|
||||
end
|
||||
|
||||
%% Infra
|
||||
subgraph InfraServices ["<b>Infra</b>"]
|
||||
DB[("<b>Metadata Database</b><br/>(Postgres / MySQL)")]
|
||||
|
||||
subgraph Caching ["<b>Caching Subservices<br/></b>(Redis, memcache, S3, ...)"]
|
||||
direction LR
|
||||
DummySpace[" "]:::invisible
|
||||
QueryCache["<b>Query Results Cache</b><br/>(Accelerated Dashboards)"]
|
||||
CsvCache["<b>CSV Exports Cache</b>"]
|
||||
ThumbnailCache["<b>Thumbnails Cache</b>"]
|
||||
AlertImageCache["<b>Alert/Report Images Cache</b>"]
|
||||
QueryCache -- " " --> CsvCache
|
||||
linkStyle 1 stroke:transparent;
|
||||
ThumbnailCache -- " " --> AlertImageCache
|
||||
linkStyle 2 stroke:transparent;
|
||||
end
|
||||
|
||||
Broker(("<b>Message Queue</b><br/>(Redis / RabbitMQ / SQS)"))
|
||||
end
|
||||
|
||||
AsyncBackend["<b>Async Workers (Celery)</b><br>required for Alerts & Reports, thumbnails, CSV exports, long-running workloads, ..."]
|
||||
|
||||
%% External DBs
|
||||
subgraph ExternalDatabases ["<b>Analytics Databases</b>"]
|
||||
direction LR
|
||||
BigQuery[(BigQuery)]
|
||||
Snowflake[(Snowflake)]
|
||||
Redshift[(Redshift)]
|
||||
Postgres[(Postgres)]
|
||||
Postgres[(... any ...)]
|
||||
end
|
||||
|
||||
%% Connections
|
||||
LB -.-> WebServers
|
||||
WebServers --> DB
|
||||
WebServers -.-> Caching
|
||||
WebServers -.-> Broker
|
||||
WebServers -.-> ExternalDatabases
|
||||
|
||||
Broker -.-> AsyncBackend
|
||||
|
||||
AsyncBackend -.-> ExternalDatabases
|
||||
AsyncBackend -.-> Caching
|
||||
|
||||
|
||||
|
||||
%% Legend styling
|
||||
classDef requiredNode stroke-width:2px,stroke:black;
|
||||
class Required requiredNode;
|
||||
class Optional optionalNode;
|
||||
|
||||
%% Hide real arrow
|
||||
linkStyle 0 stroke:transparent;
|
||||
|
||||
%% Styling
|
||||
classDef optionalNode stroke-dasharray: 5 5, opacity:0.9;
|
||||
class LB optionalNode;
|
||||
class Caching optionalNode;
|
||||
class AsyncBackend optionalNode;
|
||||
class Broker optionalNode;
|
||||
class QueryCache optionalNode;
|
||||
class CsvCache optionalNode;
|
||||
class ThumbnailCache optionalNode;
|
||||
class AlertImageCache optionalNode;
|
||||
class Celery optionalNode;
|
||||
|
||||
classDef invisible fill:transparent,stroke:transparent;
|
||||
```
|
||||
|
||||
## Entity-Relationship Diagram
|
||||
|
||||
For the full interactive Entity-Relationship Diagram, please visit the [main documentation](https://superset.apache.org/docs/contributing/resources).
|
||||
|
||||
You can also [download the .svg](https://github.com/apache/superset/tree/master/docs/static/img/erd.svg) directly from GitHub.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### Official Documentation
|
||||
- [Apache Superset Documentation](https://superset.apache.org/docs/intro)
|
||||
- [API Documentation](https://superset.apache.org/docs/api)
|
||||
- [Configuration Guide](https://superset.apache.org/docs/installation/configuring-superset)
|
||||
|
||||
### Community Resources
|
||||
- [Apache Superset Blog](https://preset.io/blog/)
|
||||
- [YouTube Channel](https://www.youtube.com/channel/UCMuwrvBsg_jjI2gLcm04R0g)
|
||||
- [Twitter/X](https://twitter.com/ApacheSuperset)
|
||||
|
||||
### Development Tools
|
||||
- [GitHub Repository](https://github.com/apache/superset)
|
||||
- [PyPI Package](https://pypi.org/project/apache-superset/)
|
||||
- [Docker Hub](https://hub.docker.com/r/apache/superset)
|
||||
- [npm Packages](https://www.npmjs.com/search?q=%40superset-ui)
|
||||
321
docs/developer_portal/contributing/submitting-pr.md
Normal file
321
docs/developer_portal/contributing/submitting-pr.md
Normal file
@@ -0,0 +1,321 @@
|
||||
---
|
||||
title: Submitting Pull Requests
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Submitting Pull Requests
|
||||
|
||||
Learn how to create and submit high-quality pull requests to Apache Superset.
|
||||
|
||||
## Before You Start
|
||||
|
||||
### Prerequisites
|
||||
- [ ] Development environment is set up
|
||||
- [ ] You've forked and cloned the repository
|
||||
- [ ] You've read the [contributing overview](./overview)
|
||||
- [ ] You've found or created an issue to work on
|
||||
|
||||
### PR Readiness Checklist
|
||||
- [ ] Code follows [coding guidelines](../guidelines/design-guidelines)
|
||||
- [ ] Tests are passing locally
|
||||
- [ ] Linting passes (`pre-commit run --all-files`)
|
||||
- [ ] Documentation is updated if needed
|
||||
|
||||
## Creating Your Pull Request
|
||||
|
||||
### 1. Create a Feature Branch
|
||||
|
||||
```bash
|
||||
# Update your fork
|
||||
git fetch upstream
|
||||
git checkout master
|
||||
git merge upstream/master
|
||||
git push origin master
|
||||
|
||||
# Create feature branch
|
||||
git checkout -b feature/your-feature-name
|
||||
```
|
||||
|
||||
### 2. Make Your Changes
|
||||
|
||||
```bash
|
||||
# Make changes
|
||||
edit files...
|
||||
|
||||
# Run tests
|
||||
pytest tests/unit_tests/
|
||||
cd superset-frontend && npm run test
|
||||
|
||||
# Run linting
|
||||
pre-commit run --all-files
|
||||
|
||||
# Commit with conventional format
|
||||
git add .
|
||||
git commit -m "feat(dashboard): add new filter component"
|
||||
```
|
||||
|
||||
### 3. PR Title Format
|
||||
|
||||
Follow [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
type(scope): description
|
||||
```
|
||||
|
||||
**Types:**
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation only
|
||||
- `style`: Code style (formatting, semicolons, etc.)
|
||||
- `refactor`: Code refactoring
|
||||
- `perf`: Performance improvement
|
||||
- `test`: Adding tests
|
||||
- `chore`: Maintenance tasks
|
||||
- `ci`: CI/CD changes
|
||||
- `build`: Build system changes
|
||||
- `revert`: Reverting changes
|
||||
|
||||
**Scopes:**
|
||||
- `dashboard`: Dashboard functionality
|
||||
- `sqllab`: SQL Lab features
|
||||
- `explore`: Chart explorer
|
||||
- `chart`: Visualization components
|
||||
- `api`: REST API endpoints
|
||||
- `db`: Database connections
|
||||
- `security`: Security features
|
||||
- `config`: Configuration
|
||||
|
||||
**Examples:**
|
||||
```
|
||||
feat(sqllab): add query cost estimation
|
||||
fix(dashboard): resolve filter cascading issue
|
||||
docs(api): update REST endpoint documentation
|
||||
refactor(explore): simplify chart controls logic
|
||||
perf(dashboard): optimize chart loading
|
||||
```
|
||||
|
||||
### 4. PR Description Template
|
||||
|
||||
Use the template from `.github/PULL_REQUEST_TEMPLATE.md`:
|
||||
|
||||
```markdown
|
||||
### SUMMARY
|
||||
Brief description of changes and motivation.
|
||||
|
||||
### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
|
||||
[Required for UI changes]
|
||||
|
||||
### TESTING INSTRUCTIONS
|
||||
1. Step-by-step instructions
|
||||
2. How to verify the fix/feature
|
||||
3. Any specific test scenarios
|
||||
|
||||
### ADDITIONAL INFORMATION
|
||||
- [ ] Has associated issue: #12345
|
||||
- [ ] Required feature flags:
|
||||
- [ ] API changes:
|
||||
- [ ] DB migration required:
|
||||
|
||||
### CHECKLIST
|
||||
- [ ] CI checks pass
|
||||
- [ ] Tests added/updated
|
||||
- [ ] Documentation updated
|
||||
- [ ] PR title follows conventions
|
||||
```
|
||||
|
||||
### 5. Submit the PR
|
||||
|
||||
```bash
|
||||
# Push to your fork
|
||||
git push origin feature/your-feature-name
|
||||
|
||||
# Create PR via GitHub CLI
|
||||
gh pr create --title "feat(sqllab): add query cost estimation" \
|
||||
--body-file .github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
# Or use the GitHub web interface
|
||||
```
|
||||
|
||||
## PR Best Practices
|
||||
|
||||
### Keep PRs Focused
|
||||
- One feature/fix per PR
|
||||
- Break large changes into smaller PRs
|
||||
- Separate refactoring from feature changes
|
||||
|
||||
### Write Good Commit Messages
|
||||
```bash
|
||||
# Good
|
||||
git commit -m "fix(dashboard): prevent duplicate API calls when filters change"
|
||||
|
||||
# Bad
|
||||
git commit -m "fix bug"
|
||||
git commit -m "updates"
|
||||
```
|
||||
|
||||
### Include Tests
|
||||
```python
|
||||
# Backend test example
|
||||
def test_new_feature():
|
||||
"""Test that new feature works correctly."""
|
||||
result = new_feature_function()
|
||||
assert result == expected_value
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Frontend test example
|
||||
test('renders new component', () => {
|
||||
const { getByText } = render(<NewComponent />);
|
||||
expect(getByText('Expected Text')).toBeInTheDocument();
|
||||
});
|
||||
```
|
||||
|
||||
### Add Screenshots for UI Changes
|
||||
```markdown
|
||||
### Before
|
||||

|
||||
|
||||
### After
|
||||

|
||||
```
|
||||
|
||||
### Update Documentation
|
||||
- Update relevant docs in `/docs` directory
|
||||
- Add docstrings to new functions/classes
|
||||
- Update UPDATING.md for breaking changes
|
||||
|
||||
## CI Checks
|
||||
|
||||
### Required Checks
|
||||
All PRs must pass:
|
||||
- `Python Tests` - Backend unit/integration tests
|
||||
- `Frontend Tests` - JavaScript/TypeScript tests
|
||||
- `Linting` - Code style checks
|
||||
- `Type Checking` - MyPy and TypeScript
|
||||
- `License Check` - Apache license headers
|
||||
- `Documentation Build` - Docs compile successfully
|
||||
|
||||
### Common CI Failures
|
||||
|
||||
#### Python Test Failures
|
||||
```bash
|
||||
# Run locally to debug
|
||||
pytest tests/unit_tests/ -v
|
||||
pytest tests/integration_tests/ -v
|
||||
```
|
||||
|
||||
#### Frontend Test Failures
|
||||
```bash
|
||||
cd superset-frontend
|
||||
npm run test -- --coverage
|
||||
```
|
||||
|
||||
#### Linting Failures
|
||||
```bash
|
||||
# Auto-fix many issues
|
||||
pre-commit run --all-files
|
||||
|
||||
# Manual fixes may be needed for:
|
||||
# - MyPy type errors
|
||||
# - Complex ESLint issues
|
||||
# - License headers
|
||||
```
|
||||
|
||||
## Responding to Reviews
|
||||
|
||||
### Address Feedback Promptly
|
||||
```bash
|
||||
# Make requested changes
|
||||
edit files...
|
||||
|
||||
# Add commits (don't amend during review)
|
||||
git add .
|
||||
git commit -m "fix: address review feedback"
|
||||
git push origin feature/your-feature-name
|
||||
```
|
||||
|
||||
### Request Re-review
|
||||
- Click "Re-request review" after addressing feedback
|
||||
- Comment on resolved discussions
|
||||
- Thank reviewers for their time
|
||||
|
||||
### Handling Conflicts
|
||||
```bash
|
||||
# Update your branch
|
||||
git fetch upstream
|
||||
git rebase upstream/master
|
||||
|
||||
# Resolve conflicts
|
||||
edit conflicted files...
|
||||
git add .
|
||||
git rebase --continue
|
||||
|
||||
# Force push (only to your feature branch!)
|
||||
git push --force-with-lease origin feature/your-feature-name
|
||||
```
|
||||
|
||||
## After Merge
|
||||
|
||||
### Clean Up
|
||||
```bash
|
||||
# Delete local branch
|
||||
git checkout master
|
||||
git branch -d feature/your-feature-name
|
||||
|
||||
# Delete remote branch
|
||||
git push origin --delete feature/your-feature-name
|
||||
|
||||
# Update your fork
|
||||
git fetch upstream
|
||||
git merge upstream/master
|
||||
git push origin master
|
||||
```
|
||||
|
||||
### Follow Up
|
||||
- Monitor for any issues reported
|
||||
- Help with documentation if needed
|
||||
- Consider related improvements
|
||||
|
||||
## Tips for Success
|
||||
|
||||
### Do
|
||||
- ✅ Keep PRs small and focused
|
||||
- ✅ Write descriptive PR titles and descriptions
|
||||
- ✅ Include tests for new functionality
|
||||
- ✅ Respond to feedback constructively
|
||||
- ✅ Update documentation
|
||||
- ✅ Be patient with the review process
|
||||
|
||||
### Don't
|
||||
- ❌ Submit PRs with failing tests
|
||||
- ❌ Include unrelated changes
|
||||
- ❌ Force push to master
|
||||
- ❌ Ignore CI failures
|
||||
- ❌ Skip the PR template
|
||||
|
||||
## Getting Help
|
||||
|
||||
- **Slack**: Ask in #development or #beginners
|
||||
- **GitHub**: Tag @apache/superset-committers for attention
|
||||
- **Mailing List**: dev@superset.apache.org
|
||||
|
||||
Next: [Understanding code review process](./code-review)
|
||||
@@ -1,464 +0,0 @@
|
||||
---
|
||||
title: Extension Examples
|
||||
sidebar_position: 1
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
<!--
|
||||
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 Examples
|
||||
|
||||
Learn from real-world extension implementations that showcase different capabilities of the Superset extension system.
|
||||
|
||||
## Dataset References Panel
|
||||
|
||||
A SQL Lab panel that analyzes queries and displays information about referenced tables.
|
||||
|
||||
### Features
|
||||
- Parses SQL to extract table references
|
||||
- Shows table owners and permissions
|
||||
- Displays last partition information
|
||||
- Provides row count estimates
|
||||
|
||||
### Key Implementation
|
||||
|
||||
```typescript
|
||||
// Parse SQL and extract tables
|
||||
function extractTables(sql: string): TableReference[] {
|
||||
const tables = [];
|
||||
const tableRegex = /FROM\s+(\w+\.?\w+)/gi;
|
||||
let match;
|
||||
|
||||
while ((match = tableRegex.exec(sql)) !== null) {
|
||||
tables.push({
|
||||
schema: match[1].split('.')[0],
|
||||
table: match[1].split('.')[1] || match[1],
|
||||
});
|
||||
}
|
||||
|
||||
return tables;
|
||||
}
|
||||
|
||||
// Register panel
|
||||
export function activate(context: ExtensionContext) {
|
||||
const panel = context.core.registerView('dataset-references.panel', () => (
|
||||
<DatasetReferencesPanel />
|
||||
));
|
||||
|
||||
// Listen for query changes
|
||||
const listener = context.sqlLab.onDidChangeEditorContent((content) => {
|
||||
const tables = extractTables(content);
|
||||
updatePanelWithTables(tables);
|
||||
});
|
||||
|
||||
context.subscriptions.push(panel, listener);
|
||||
}
|
||||
```
|
||||
|
||||
### Manifest
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "dataset-references",
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab.panels": [{
|
||||
"id": "dataset-references.panel",
|
||||
"name": "Dataset References",
|
||||
"icon": "DatabaseOutlined",
|
||||
"location": "right"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Query Optimizer
|
||||
|
||||
Analyzes SQL queries and suggests optimizations.
|
||||
|
||||
### Features
|
||||
- Detects missing indexes
|
||||
- Suggests query rewrites
|
||||
- Identifies expensive operations
|
||||
- Provides execution plan analysis
|
||||
|
||||
### Implementation Highlights
|
||||
|
||||
```typescript
|
||||
// Register optimization command
|
||||
const optimizeCommand = context.commands.registerCommand('query-optimizer.analyze', {
|
||||
title: 'Analyze Query Performance',
|
||||
icon: 'ThunderboltOutlined',
|
||||
execute: async () => {
|
||||
const query = context.sqlLab.getCurrentQuery();
|
||||
const database = context.sqlLab.getCurrentDatabase();
|
||||
|
||||
// Get execution plan
|
||||
const plan = await getExecutionPlan(database.id, query);
|
||||
|
||||
// Analyze and suggest improvements
|
||||
const suggestions = analyzeExecutionPlan(plan);
|
||||
|
||||
// Show results in panel
|
||||
showOptimizationResults(suggestions);
|
||||
}
|
||||
});
|
||||
|
||||
// Add to editor menu
|
||||
"menus": {
|
||||
"sqllab.editor": {
|
||||
"primary": [{
|
||||
"command": "query-optimizer.analyze",
|
||||
"when": "editorHasContent"
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Natural Language to SQL
|
||||
|
||||
Converts natural language questions to SQL queries using LLM integration.
|
||||
|
||||
### Features
|
||||
- Natural language input
|
||||
- Context-aware SQL generation
|
||||
- Query validation
|
||||
- History tracking
|
||||
|
||||
### Key Components
|
||||
|
||||
```typescript
|
||||
// Backend API endpoint
|
||||
@rest_api.route('/nl2sql/generate')
|
||||
def generate_sql(prompt: str, context: dict):
|
||||
# Use LLM to generate SQL
|
||||
sql = llm_client.generate(
|
||||
prompt=prompt,
|
||||
schema=context['schema'],
|
||||
examples=context['examples']
|
||||
)
|
||||
|
||||
# Validate generated SQL
|
||||
validation = validate_sql(sql)
|
||||
|
||||
return {
|
||||
'sql': sql,
|
||||
'valid': validation.is_valid,
|
||||
'errors': validation.errors
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Frontend integration
|
||||
function NL2SQLPanel() {
|
||||
const [prompt, setPrompt] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const generateSQL = async () => {
|
||||
setLoading(true);
|
||||
|
||||
const response = await context.network.api.post('/extensions/nl2sql/generate', {
|
||||
prompt,
|
||||
context: {
|
||||
database: context.sqlLab.getCurrentDatabase(),
|
||||
schema: await context.sqlLab.getCurrentSchema(),
|
||||
}
|
||||
});
|
||||
|
||||
if (response.valid) {
|
||||
// Insert SQL into editor
|
||||
context.sqlLab.insertText(response.sql);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input.TextArea
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
placeholder="Describe what data you want..."
|
||||
/>
|
||||
<Button onClick={generateSQL} loading={loading}>
|
||||
Generate SQL
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Schema Visualizer
|
||||
|
||||
Interactive database schema visualization.
|
||||
|
||||
### Features
|
||||
- Visual ERD diagram
|
||||
- Table relationships
|
||||
- Column details on hover
|
||||
- Export to image
|
||||
|
||||
### Implementation
|
||||
|
||||
```typescript
|
||||
import { Graph } from '@antv/g6';
|
||||
|
||||
function SchemaVisualizer() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [graph, setGraph] = useState<Graph>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const g = new Graph({
|
||||
container: containerRef.current,
|
||||
layout: {
|
||||
type: 'dagre',
|
||||
rankdir: 'LR',
|
||||
},
|
||||
defaultNode: {
|
||||
type: 'sql-table-node',
|
||||
},
|
||||
defaultEdge: {
|
||||
type: 'sql-relation-edge',
|
||||
},
|
||||
});
|
||||
|
||||
setGraph(g);
|
||||
loadSchemaData(g);
|
||||
|
||||
return () => g.destroy();
|
||||
}, []);
|
||||
|
||||
const loadSchemaData = async (g: Graph) => {
|
||||
const tables = await context.sqlLab.getTables();
|
||||
const nodes = tables.map(table => ({
|
||||
id: table.name,
|
||||
label: table.name,
|
||||
columns: table.columns,
|
||||
}));
|
||||
|
||||
const edges = extractRelationships(tables);
|
||||
|
||||
g.data({ nodes, edges });
|
||||
g.render();
|
||||
};
|
||||
|
||||
return <div ref={containerRef} style={{ height: '100%' }} />;
|
||||
}
|
||||
```
|
||||
|
||||
## SQL Formatter
|
||||
|
||||
Formats and beautifies SQL code with customizable rules.
|
||||
|
||||
### Features
|
||||
- Multiple formatting styles
|
||||
- Custom rule configuration
|
||||
- Batch formatting
|
||||
- Format on save
|
||||
|
||||
### Simple Implementation
|
||||
|
||||
```typescript
|
||||
import { format } from 'sql-formatter';
|
||||
|
||||
const formatCommand = context.commands.registerCommand('sql-formatter.format', {
|
||||
title: 'Format SQL',
|
||||
execute: () => {
|
||||
const sql = context.sqlLab.getCurrentQuery();
|
||||
|
||||
const formatted = format(sql, {
|
||||
language: 'sql',
|
||||
indent: ' ',
|
||||
uppercase: true,
|
||||
linesBetweenQueries: 2,
|
||||
});
|
||||
|
||||
context.sqlLab.replaceQuery(formatted);
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-format on save
|
||||
context.sqlLab.onWillSaveQuery((event) => {
|
||||
if (context.storage.local.get('autoFormat')) {
|
||||
const formatted = format(event.query);
|
||||
event.waitUntil(Promise.resolve(formatted));
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Query History Search
|
||||
|
||||
Enhanced query history with advanced search and filtering.
|
||||
|
||||
### Features
|
||||
- Full-text search
|
||||
- Filter by date, user, database
|
||||
- Query statistics
|
||||
- Export capabilities
|
||||
|
||||
### UI Component
|
||||
|
||||
```typescript
|
||||
function QueryHistoryPanel() {
|
||||
const [queries, setQueries] = useState<Query[]>([]);
|
||||
const [filters, setFilters] = useState<Filters>({});
|
||||
|
||||
useEffect(() => {
|
||||
loadQueries();
|
||||
}, [filters]);
|
||||
|
||||
const loadQueries = async () => {
|
||||
const history = await context.network.api.get('/api/v1/query', {
|
||||
params: {
|
||||
...filters,
|
||||
page_size: 100,
|
||||
}
|
||||
});
|
||||
|
||||
setQueries(history.result);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SearchFilters onChange={setFilters} />
|
||||
<Table
|
||||
dataSource={queries}
|
||||
columns={[
|
||||
{ title: 'Query', dataIndex: 'sql', ellipsis: true },
|
||||
{ title: 'Database', dataIndex: 'database' },
|
||||
{ title: 'Status', dataIndex: 'status' },
|
||||
{ title: 'Duration', dataIndex: 'duration' },
|
||||
{ title: 'User', dataIndex: 'user' },
|
||||
{
|
||||
title: 'Actions',
|
||||
render: (query) => (
|
||||
<Button
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => context.sqlLab.insertText(query.sql)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Git Integration
|
||||
|
||||
Version control for SQL queries and dashboards.
|
||||
|
||||
### Features
|
||||
- Save queries to Git
|
||||
- Track changes
|
||||
- Collaborative editing
|
||||
- Branch management
|
||||
|
||||
### Backend Integration
|
||||
|
||||
```python
|
||||
from git import Repo
|
||||
|
||||
class GitExtension:
|
||||
def __init__(self, repo_path):
|
||||
self.repo = Repo(repo_path)
|
||||
|
||||
def save_query(self, query, message):
|
||||
# Save query to file
|
||||
path = f"queries/{query.name}.sql"
|
||||
with open(path, 'w') as f:
|
||||
f.write(query.sql)
|
||||
|
||||
# Commit to Git
|
||||
self.repo.index.add([path])
|
||||
self.repo.index.commit(message)
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'commit': self.repo.head.commit.hexsha
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices from Examples
|
||||
|
||||
### 1. User Experience
|
||||
- Provide clear feedback for async operations
|
||||
- Handle errors gracefully
|
||||
- Include loading states
|
||||
- Add keyboard shortcuts
|
||||
|
||||
### 2. Performance
|
||||
- Debounce expensive operations
|
||||
- Cache API responses
|
||||
- Use virtual scrolling for large lists
|
||||
- Lazy load heavy components
|
||||
|
||||
### 3. Integration
|
||||
- Respect Superset's theme
|
||||
- Use provided UI components
|
||||
- Follow existing UX patterns
|
||||
- Integrate with existing menus
|
||||
|
||||
### 4. Code Organization
|
||||
```
|
||||
extension/
|
||||
├── frontend/
|
||||
│ ├── src/
|
||||
│ │ ├── components/ # UI components
|
||||
│ │ ├── hooks/ # Custom hooks
|
||||
│ │ ├── services/ # API services
|
||||
│ │ ├── utils/ # Utilities
|
||||
│ │ └── index.tsx # Entry point
|
||||
│ └── tests/
|
||||
├── backend/
|
||||
│ ├── src/
|
||||
│ │ ├── api/ # REST endpoints
|
||||
│ │ ├── models/ # Data models
|
||||
│ │ ├── services/ # Business logic
|
||||
│ │ └── entrypoint.py
|
||||
│ └── tests/
|
||||
```
|
||||
|
||||
### 5. Testing
|
||||
```typescript
|
||||
// Test example
|
||||
describe('DatasetReferences', () => {
|
||||
it('should extract tables from SQL', () => {
|
||||
const sql = 'SELECT * FROM users JOIN orders ON users.id = orders.user_id';
|
||||
const tables = extractTables(sql);
|
||||
|
||||
expect(tables).toEqual([
|
||||
{ schema: 'public', table: 'users' },
|
||||
{ schema: 'public', table: 'orders' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
- [API Reference](../api/frontend)
|
||||
- [Architecture Overview](../architecture/overview)
|
||||
- [Getting Started Guide](../getting-started)
|
||||
- [CLI Documentation](../cli/overview)
|
||||
253
docs/developer_portal/extensions/architecture.md
Normal file
253
docs/developer_portal/extensions/architecture.md
Normal file
@@ -0,0 +1,253 @@
|
||||
---
|
||||
title: Architecture
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Architecture
|
||||
|
||||
Apache Superset's extension system is designed to enable powerful customization while maintaining stability, security, and performance. This page explains the architectural principles, system design, and technical mechanisms that make the extension ecosystem possible.
|
||||
|
||||
## Architectural Principles
|
||||
|
||||
The extension architecture is built on six core principles that guide all technical decisions and ensure extensions can be developed safely and predictably:
|
||||
|
||||
### 1. Lean Core
|
||||
|
||||
Superset's core should remain minimal, with many features delegated to extensions. Built-in features use the same APIs and extension mechanisms available to external developers. This approach:
|
||||
- Reduces maintenance burden and complexity
|
||||
- Encourages modularity
|
||||
- Allows the community to innovate independently of the main codebase
|
||||
|
||||
### 2. Explicit Contribution Points
|
||||
|
||||
All extension points are clearly defined and documented. Extension authors know exactly where and how they can interact with the host system. Each extension declares its capabilities in a metadata file, enabling the host to:
|
||||
- Manage the extension lifecycle
|
||||
- Provide a consistent user experience
|
||||
- Validate extension compatibility
|
||||
|
||||
### 3. Versioned and Stable APIs
|
||||
|
||||
Public interfaces for extensions follow semantic versioning, allowing for:
|
||||
- Safe evolution of the platform
|
||||
- Backward compatibility
|
||||
- Clear upgrade paths for extension authors
|
||||
|
||||
### 4. Lazy Loading and Activation
|
||||
|
||||
Extensions are loaded and activated only when needed, which:
|
||||
- Minimizes performance overhead
|
||||
- Reduces resource consumption
|
||||
- Improves startup time
|
||||
|
||||
### 5. Composability and Reuse
|
||||
|
||||
The architecture encourages reusing extension points and patterns across different modules, promoting:
|
||||
- Consistency across extensions
|
||||
- Reduced duplication
|
||||
- Shared best practices
|
||||
|
||||
### 6. Community-Driven Evolution
|
||||
|
||||
The system evolves based on real-world feedback and contributions. New extension points and capabilities are added as needs emerge, ensuring the platform remains relevant and flexible.
|
||||
|
||||
## System Overview
|
||||
|
||||
The extension architecture is built around three main components that work together to create a flexible, maintainable ecosystem:
|
||||
|
||||
### Core Packages
|
||||
|
||||
Two core packages provide the foundation for extension development:
|
||||
|
||||
**Frontend: `@apache-superset/core`**
|
||||
|
||||
This package provides essential building blocks for frontend extensions and the host application:
|
||||
- Shared UI components
|
||||
- Utility functions
|
||||
- APIs and hooks
|
||||
- Type definitions
|
||||
|
||||
By centralizing these resources, both extensions and built-in features use the same APIs, ensuring consistency, type safety, and a seamless user experience. The package is versioned to support safe platform evolution while maintaining compatibility.
|
||||
|
||||
**Backend: `apache-superset-core`**
|
||||
|
||||
This package exposes key classes and APIs for backend extensions:
|
||||
- Database connectors
|
||||
- API extensions
|
||||
- Security manager customization
|
||||
- Core utilities and models
|
||||
|
||||
It includes dependencies on critical libraries like Flask-AppBuilder and SQLAlchemy, and follows semantic versioning for compatibility and stability.
|
||||
|
||||
### Developer Tools
|
||||
|
||||
**`apache-superset-extensions-cli`**
|
||||
|
||||
The CLI provides comprehensive commands for extension development:
|
||||
- Project scaffolding
|
||||
- Code generation
|
||||
- Building and bundling
|
||||
- Packaging for distribution
|
||||
|
||||
By standardizing these processes, the CLI ensures extensions are built consistently, remain compatible with evolving versions of Superset, and follow best practices.
|
||||
|
||||
### Host Application
|
||||
|
||||
The Superset host application serves as the runtime environment for extensions:
|
||||
|
||||
**Extension Management**
|
||||
- Exposes `/api/v1/extensions` endpoint for registration and management
|
||||
- Provides a dedicated UI for managing extensions
|
||||
- Stores extension metadata in the `extensions` database table
|
||||
|
||||
**Extension Storage**
|
||||
|
||||
The extensions table contains:
|
||||
- Extension name, version, and author
|
||||
- Contributed features and exposed modules
|
||||
- Metadata and configuration
|
||||
- Built frontend and/or backend code
|
||||
|
||||
### Architecture Diagram
|
||||
|
||||
The following diagram illustrates how these components work together:
|
||||
|
||||
<img width="955" height="586" alt="Extension System Architecture" src="https://github.com/user-attachments/assets/cc2a41df-55a4-48c8-b056-35f7a1e567c6" />
|
||||
|
||||
The diagram shows:
|
||||
1. **Extension projects** depend on core packages for development
|
||||
2. **Core packages** provide APIs and type definitions
|
||||
3. **The host application** implements the APIs and manages extensions
|
||||
4. **Extensions** integrate seamlessly with the host through well-defined interfaces
|
||||
|
||||
### Extension Dependencies
|
||||
|
||||
Extensions can depend on any combination of packages based on their needs. For example:
|
||||
|
||||
**Frontend-only extension** (e.g., a custom chart type):
|
||||
- Depends on `@apache-superset/core` for UI components and React APIs
|
||||
|
||||
**Full-stack extension** (e.g., a custom SQL editor with new API endpoints):
|
||||
- Depends on `@apache-superset/core` for frontend components
|
||||
- Depends on `apache-superset-core` for backend APIs and models
|
||||
|
||||
This modular approach allows extension authors to choose exactly what they need while promoting consistency and reusability.
|
||||
|
||||
## Dynamic Module Loading
|
||||
|
||||
One of the most sophisticated aspects of the extension architecture is how frontend code is dynamically loaded at runtime using Webpack's Module Federation.
|
||||
|
||||
### Module Federation
|
||||
|
||||
The architecture leverages Webpack's Module Federation to enable dynamic loading of frontend assets. This allows extensions to be built independently from Superset.
|
||||
|
||||
### How It Works
|
||||
|
||||
**Extension Configuration**
|
||||
|
||||
Extensions configure Webpack to expose their entry points:
|
||||
|
||||
``` typescript
|
||||
new ModuleFederationPlugin({
|
||||
name: 'my_extension',
|
||||
filename: 'remoteEntry.[contenthash].js',
|
||||
exposes: {
|
||||
'./index': './src/index.tsx',
|
||||
},
|
||||
externalsType: 'window',
|
||||
externals: {
|
||||
'@apache-superset/core': 'superset',
|
||||
},
|
||||
shared: {
|
||||
react: { singleton: true },
|
||||
'react-dom': { singleton: true },
|
||||
'antd-v5': { singleton: true }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
This configuration does several important things:
|
||||
|
||||
**`exposes`** - Declares which modules are available to the host application. The extension makes `./index` available as its entry point.
|
||||
|
||||
**`externals` and `externalsType`** - Tell Webpack that when the extension imports `@apache-superset/core`, it should use `window.superset` at runtime instead of bundling its own copy. This ensures extensions use the host's implementation of shared packages.
|
||||
|
||||
**`shared`** - Prevents duplication of common libraries like React and Ant Design. The `singleton: true` setting ensures only one instance of each library exists, avoiding version conflicts and reducing bundle size.
|
||||
|
||||
### Runtime Resolution
|
||||
|
||||
The following diagram illustrates the module loading process:
|
||||
|
||||
<img width="913" height="558" alt="Module Federation Flow" src="https://github.com/user-attachments/assets/e5e4d2ae-e8b5-4d17-a2a1-3667c65f25ca" />
|
||||
|
||||
Here's what happens at runtime:
|
||||
|
||||
1. **Extension Registration**: When an extension is registered, Superset stores its remote entry URL
|
||||
2. **Dynamic Loading**: When the extension is activated, the host fetches the remote entry file
|
||||
3. **Module Resolution**: The extension imports `@apache-superset/core`, which resolves to `window.superset`
|
||||
4. **Execution**: The extension code runs with access to the host's APIs and shared dependencies
|
||||
|
||||
### Host API Setup
|
||||
|
||||
On the Superset side, the APIs are mapped to `window.superset` during application bootstrap:
|
||||
|
||||
``` typescript
|
||||
import * as supersetCore from '@apache-superset/core';
|
||||
import {
|
||||
authentication,
|
||||
core,
|
||||
commands,
|
||||
environment,
|
||||
extensions,
|
||||
sqlLab,
|
||||
} from 'src/extensions';
|
||||
|
||||
export default function setupExtensionsAPI() {
|
||||
window.superset = {
|
||||
...supersetCore,
|
||||
authentication,
|
||||
core,
|
||||
commands,
|
||||
environment,
|
||||
extensions,
|
||||
sqlLab,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
This function runs before any extensions are loaded, ensuring the APIs are available when extensions import from `@apache-superset/core`.
|
||||
|
||||
### Benefits
|
||||
|
||||
This architecture provides several key benefits:
|
||||
|
||||
- **Independent development**: Extensions can be built separately from Superset's codebase
|
||||
- **Version isolation**: Each extension can be developed with its own release cycle
|
||||
- **Shared dependencies**: Common libraries are shared, reducing memory usage and bundle size
|
||||
- **Type safety**: TypeScript types flow from the core package to extensions
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you understand the architecture, explore:
|
||||
|
||||
- **[Quick Start](./quick-start)** - Build your first extension
|
||||
- **[Contribution Types](./contribution-types)** - What kinds of extensions you can build
|
||||
- **[Development](./development)** - Project structure, APIs, and development workflow
|
||||
130
docs/developer_portal/extensions/contribution-types.md
Normal file
130
docs/developer_portal/extensions/contribution-types.md
Normal file
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: Contribution Types
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Contribution Types
|
||||
|
||||
To facilitate the development of extensions, we define a set of well-defined contribution types that extensions can implement. These contribution types serve as the building blocks for extensions, allowing them to interact with the host application and provide new functionality.
|
||||
|
||||
## Frontend
|
||||
|
||||
Frontend contribution types allow extensions to extend Superset's user interface with new views, commands, and menu items.
|
||||
|
||||
### Views
|
||||
|
||||
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
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab.panels": [
|
||||
{
|
||||
"id": "my_extension.main",
|
||||
"name": "My Panel Name"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
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
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "my_extension.copy_query",
|
||||
"icon": "CopyOutlined",
|
||||
"title": "Copy Query",
|
||||
"description": "Copy the current query to clipboard"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Menus
|
||||
|
||||
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
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"menus": {
|
||||
"sqllab.editor": {
|
||||
"primary": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "my_extension.copy_query"
|
||||
}
|
||||
],
|
||||
"secondary": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "my_extension.prettify"
|
||||
}
|
||||
],
|
||||
"context": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "my_extension.clear"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Backend
|
||||
|
||||
Backend contribution types allow extensions to extend Superset's server-side capabilities with new API endpoints, MCP tools, and MCP prompts.
|
||||
|
||||
### REST API Endpoints
|
||||
|
||||
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
|
||||
"backend": {
|
||||
"entryPoints": ["my_extension.entrypoint"],
|
||||
"files": ["backend/src/my_extension/**/*.py"]
|
||||
}
|
||||
```
|
||||
|
||||
The entry point module registers the API with Superset:
|
||||
|
||||
``` python
|
||||
from superset_core.api.rest_api import add_extension_api
|
||||
from .api import MyExtensionAPI
|
||||
|
||||
add_extension_api(MyExtensionAPI)
|
||||
```
|
||||
|
||||
### MCP Tools and Prompts
|
||||
|
||||
Extensions can contribute Model Context Protocol (MCP) tools and prompts that AI agents can discover and use. See [MCP Integration](./mcp) for detailed documentation.
|
||||
49
docs/developer_portal/extensions/deployment.md
Normal file
49
docs/developer_portal/extensions/deployment.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Deployment
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Deployment
|
||||
|
||||
Once an extension has been developed, the deployment process involves packaging and uploading it to the host application.
|
||||
|
||||
Packaging is handled by the `superset-extensions bundle` command, which:
|
||||
|
||||
1. Builds frontend assets using Webpack (with Module Federation configuration).
|
||||
2. Collects backend Python source files and all necessary resources.
|
||||
3. Generates a `manifest.json` with build-time metadata, including the contents of `extension.json` and references to built assets.
|
||||
4. Packages everything into a `.supx` file (a zip archive with a specific structure required by Superset).
|
||||
|
||||
To deploy an extension, place the `.supx` file in the extensions directory configured via `EXTENSIONS_PATH` in your `superset_config.py`:
|
||||
|
||||
``` python
|
||||
EXTENSIONS_PATH = "/path/to/extensions"
|
||||
```
|
||||
|
||||
During application startup, Superset automatically discovers and loads all `.supx` files from this directory:
|
||||
|
||||
1. Scans the configured directory for `.supx` files.
|
||||
2. Validates each file is a properly formatted zip archive.
|
||||
3. Extracts and validates the extension manifest and metadata.
|
||||
4. Loads the extension, making it available for use.
|
||||
|
||||
This file-based approach simplifies deployment in containerized environments and enables version control of extensions alongside infrastructure configuration.
|
||||
239
docs/developer_portal/extensions/development.md
Normal file
239
docs/developer_portal/extensions/development.md
Normal file
@@ -0,0 +1,239 @@
|
||||
---
|
||||
title: Development
|
||||
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.
|
||||
-->
|
||||
|
||||
# Development
|
||||
|
||||
This guide covers everything you need to know about developing extensions for Superset, from project structure to development workflow.
|
||||
|
||||
## Project Structure
|
||||
|
||||
The [apache-superset-extensions-cli](https://github.com/apache/superset/tree/master/superset-extensions-cli) package provides a command-line interface (CLI) that streamlines the extension development workflow. It offers the following commands:
|
||||
|
||||
```
|
||||
superset-extensions init: Generates the initial folder structure and scaffolds a new extension project.
|
||||
|
||||
superset-extensions build: Builds extension assets.
|
||||
|
||||
superset-extensions bundle: Packages the extension into a .supx file.
|
||||
|
||||
superset-extensions dev: Automatically rebuilds the extension as files change.
|
||||
```
|
||||
|
||||
When creating a new extension with `superset-extensions init <extension-name>`, the CLI generates a standardized folder structure:
|
||||
|
||||
```
|
||||
dataset_references/
|
||||
├── extension.json
|
||||
├── frontend/
|
||||
│ ├── src/
|
||||
│ ├── webpack.config.js
|
||||
│ ├── tsconfig.json
|
||||
│ └── package.json
|
||||
├── backend/
|
||||
│ ├── src/
|
||||
│ └── dataset_references/
|
||||
│ ├── tests/
|
||||
│ ├── pyproject.toml
|
||||
│ └── requirements.txt
|
||||
├── dist/
|
||||
│ ├── manifest.json
|
||||
│ ├── frontend
|
||||
│ └── dist/
|
||||
│ ├── remoteEntry.d7a9225d042e4ccb6354.js
|
||||
│ └── 900.038b20cdff6d49cfa8d9.js
|
||||
│ └── backend
|
||||
│ └── dataset_references/
|
||||
│ ├── __init__.py
|
||||
│ ├── api.py
|
||||
│ └── entrypoint.py
|
||||
├── dataset_references-1.0.0.supx
|
||||
└── README.md
|
||||
```
|
||||
|
||||
The `extension.json` file serves as the declared metadata for the extension, containing the extension's name, version, author, description, and a list of capabilities. This file is essential for the host application to understand how to load and manage the extension.
|
||||
|
||||
The `frontend` directory contains the source code for the frontend components of the extension, including React components, styles, and assets. The `webpack.config.js` file is used to configure Webpack for building the frontend code, while the `tsconfig.json` file defines the TypeScript configuration for the project. The `package.json` file specifies the dependencies and scripts for building and testing the frontend code.
|
||||
|
||||
The `backend` directory contains the source code for the backend components of the extension, including Python modules, tests, and configuration files. The `pyproject.toml` file is used to define the Python package and its dependencies, while the `requirements.txt` file lists the required Python packages for the extension. The `src` folder contains the functional backend source files, `tests` directory contains unit tests for the backend code, ensuring that the extension behaves as expected and meets the defined requirements.
|
||||
|
||||
The `dist` directory is built when running the `build` or `dev` command, and contains the files that will be included in the bundle. The `manifest.json` file contains critical metadata about the extension, including the majority of the contents of the `extension.json` file, but also other build-time information, like the name of the built Webpack Module Federation remote entry file. The files in the `dist` directory will be zipped into the final `.supx` file. Although this file is technically a zip archive, the `.supx` extension makes it clear that it is a Superset extension package and follows a specific file layout. This packaged file can be distributed and installed in Superset instances.
|
||||
|
||||
The `README.md` file provides documentation and instructions for using the extension, including how to install, configure, and use its functionality.
|
||||
|
||||
## Extension Metadata
|
||||
|
||||
The `extension.json` file contains all metadata necessary for the host application to understand and manage the extension:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "dataset_references",
|
||||
"version": "1.0.0",
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab.panels": [
|
||||
{
|
||||
"id": "dataset_references.main",
|
||||
"name": "Dataset references"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"moduleFederation": {
|
||||
"exposes": ["./index"]
|
||||
}
|
||||
},
|
||||
"backend": {
|
||||
"entryPoints": ["dataset_references.entrypoint"],
|
||||
"files": ["backend/src/dataset_references/**/*.py"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `contributions` section declares how the extension extends Superset's functionality through views, commands, menus, and other contribution types. The `backend` section specifies entry points and files to include in the bundle.
|
||||
|
||||
## Interacting with the Host
|
||||
|
||||
Extensions interact with Superset through well-defined, versioned APIs provided by the `@apache-superset/core` (frontend) and `apache-superset-core` (backend) packages. These APIs are designed to be stable, discoverable, and consistent for both built-in and external extensions.
|
||||
|
||||
**Note**: The `superset_core.api` module provides abstract classes that are replaced with concrete implementations via dependency injection when Superset initializes. This allows extensions to use the same interfaces as the host application.
|
||||
|
||||
### Frontend APIs
|
||||
|
||||
The frontend extension APIs (via `@apache-superset/core`) are organized into logical namespaces such as `authentication`, `commands`, `extensions`, `sqlLab`, and others. Each namespace groups related functionality, making it easy for extension authors to discover and use the APIs relevant to their needs. For example, the `sqlLab` namespace provides events and methods specific to SQL Lab, allowing extensions to react to user actions and interact with the SQL Lab environment:
|
||||
|
||||
```typescript
|
||||
export const getCurrentTab: () => Tab | undefined;
|
||||
|
||||
export const getDatabases: () => Database[];
|
||||
|
||||
export const getTabs: () => Tab[];
|
||||
|
||||
export const onDidChangeEditorContent: Event<string>;
|
||||
|
||||
export const onDidClosePanel: Event<Panel>;
|
||||
|
||||
export const onDidChangeActivePanel: Event<Panel>;
|
||||
|
||||
export const onDidChangeTabTitle: Event<string>;
|
||||
|
||||
export const onDidQueryRun: Event<Editor>;
|
||||
|
||||
export const onDidQueryStop: Event<Editor>;
|
||||
```
|
||||
|
||||
The following code demonstrates more examples of the existing frontend APIs:
|
||||
|
||||
```typescript
|
||||
import { core, commands, sqlLab, authentication, Button } from '@apache-superset/core';
|
||||
import MyPanel from './MyPanel';
|
||||
|
||||
export function activate(context) {
|
||||
// Register a new panel (view) in SQL Lab and use shared UI components in your extension's React code
|
||||
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: () => {
|
||||
// Command logic here
|
||||
},
|
||||
});
|
||||
|
||||
// Listen for query run events in SQL Lab
|
||||
const eventDisposable = sqlLab.onDidQueryRun(editor => {
|
||||
// Handle query execution event
|
||||
});
|
||||
|
||||
// Access a CSRF token for secure API requests
|
||||
authentication.getCSRFToken().then(token => {
|
||||
// Use token as needed
|
||||
});
|
||||
|
||||
// Add all disposables for automatic cleanup on deactivation
|
||||
context.subscriptions.push(panelDisposable, commandDisposable, eventDisposable);
|
||||
}
|
||||
```
|
||||
|
||||
### Backend APIs
|
||||
|
||||
Backend APIs (via `apache-superset-core`) follow a similar pattern, providing access to Superset's models, sessions, and query capabilities. Extensions can register REST API endpoints, access the metadata database, and interact with Superset's core functionality.
|
||||
|
||||
Extension endpoints are registered under a dedicated `/extensions` namespace to avoid conflicting with built-in endpoints and also because they don't share the same version constraints. By grouping all extension endpoints under `/extensions`, Superset establishes a clear boundary between core and extension functionality, making it easier to manage, document, and secure both types of APIs.
|
||||
|
||||
```python
|
||||
from superset_core.api.models import Database, get_session
|
||||
from superset_core.api.daos import DatabaseDAO
|
||||
from superset_core.api.rest_api import add_extension_api
|
||||
from .api import DatasetReferencesAPI
|
||||
|
||||
# Register a new extension REST API
|
||||
add_extension_api(DatasetReferencesAPI)
|
||||
|
||||
# Fetch Superset entities via the DAO to apply base filters that filter out entities
|
||||
# that the user doesn't have access to
|
||||
databases = DatabaseDAO.find_all()
|
||||
|
||||
# ..or apply simple filters on top of base filters
|
||||
databases = DatabaseDAO.filter_by(uuid=database.uuid)
|
||||
if not databases:
|
||||
raise Exception("Database not found")
|
||||
|
||||
return databases[0]
|
||||
|
||||
# Perform complex queries using SQLAlchemy Query, also filtering out
|
||||
# inaccessible entities
|
||||
session = get_session()
|
||||
databases_query = session.query(Database).filter(
|
||||
Database.database_name.ilike("%abc%")
|
||||
)
|
||||
return DatabaseDAO.query(databases_query)
|
||||
```
|
||||
|
||||
In the future, we plan to expand the backend APIs to support configuring security models, database engines, SQL Alchemy dialects, etc.
|
||||
|
||||
## Development Mode
|
||||
|
||||
Development mode accelerates extension development by letting developers see changes in Superset quickly, without the need for repeated packaging and uploading. To enable development mode, set the `LOCAL_EXTENSIONS` configuration in your `superset_config.py`:
|
||||
|
||||
```python
|
||||
LOCAL_EXTENSIONS = [
|
||||
"/path/to/your/extension1",
|
||||
"/path/to/your/extension2",
|
||||
]
|
||||
```
|
||||
|
||||
This instructs Superset to load and serve extensions directly from disk, so you can iterate quickly. Running `superset-extensions dev` watches for file changes and rebuilds assets automatically, while the Webpack development server (started separately with `npm run dev-server`) serves updated files as soon as they're modified. This enables immediate feedback for React components, styles, and other frontend code. Changes to backend files are also detected automatically and immediately synced, ensuring that both frontend and backend updates are reflected in your development environment.
|
||||
|
||||
Example output when running in development mode:
|
||||
|
||||
```
|
||||
superset-extensions dev
|
||||
|
||||
⚙️ Building frontend assets…
|
||||
✅ Frontend rebuilt
|
||||
✅ Backend files synced
|
||||
✅ Manifest updated
|
||||
👀 Watching for changes in: /dataset_references/frontend, /dataset_references/backend
|
||||
```
|
||||
209
docs/developer_portal/extensions/extension-points/sqllab.md
Normal file
209
docs/developer_portal/extensions/extension-points/sqllab.md
Normal file
@@ -0,0 +1,209 @@
|
||||
---
|
||||
title: SQL Lab
|
||||
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.
|
||||
-->
|
||||
|
||||
# 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.
|
||||
|
||||
## Layout Overview
|
||||
|
||||
```
|
||||
┌──────────┬─────────────────────────────────────────┬─────────────┐
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ Editor │ │
|
||||
│ │ │ │
|
||||
│ Left │ │ Right │
|
||||
│ Sidebar ├─────────────────────────────────────────┤ Sidebar │
|
||||
│ │ │ │
|
||||
│ │ Panels │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
├──────────┴─────────────────────────────────────────┴─────────────┤
|
||||
│ 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 |
|
||||
|
||||
## Area Customizations
|
||||
|
||||
Each extension point area supports three types of action customizations:
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ Area Title [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 |
|
||||
|
||||
## Examples
|
||||
|
||||
### Adding a Panel
|
||||
|
||||
This example adds a "Data Profiler" panel to SQL Lab:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "data_profiler",
|
||||
"version": "1.0.0",
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab.panels": [
|
||||
{
|
||||
"id": "data_profiler.main",
|
||||
"name": "Data Profiler"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { core } from '@apache-superset/core';
|
||||
import DataProfilerPanel from './DataProfilerPanel';
|
||||
|
||||
export function activate(context) {
|
||||
// Register the panel view with the ID declared in extension.json
|
||||
const disposable = core.registerView('data_profiler.main', <DataProfilerPanel />);
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
```
|
||||
|
||||
### Adding Actions to the Editor
|
||||
|
||||
This example adds primary, secondary, and context actions to the editor:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "query_tools",
|
||||
"version": "1.0.0",
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "query_tools.format",
|
||||
"title": "Format Query",
|
||||
"icon": "FormatPainterOutlined"
|
||||
},
|
||||
{
|
||||
"command": "query_tools.explain",
|
||||
"title": "Explain Query"
|
||||
},
|
||||
{
|
||||
"command": "query_tools.copy_as_cte",
|
||||
"title": "Copy as CTE"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"sqllab.editor": {
|
||||
"primary": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "query_tools.format"
|
||||
}
|
||||
],
|
||||
"secondary": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "query_tools.explain"
|
||||
}
|
||||
],
|
||||
"context": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "query_tools.copy_as_cte"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
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 tab = sqlLab.getCurrentTab();
|
||||
if (tab?.editor) {
|
||||
// Format the SQL query
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const explainCommand = commands.registerCommand('query_tools.explain', {
|
||||
execute: () => {
|
||||
const tab = sqlLab.getCurrentTab();
|
||||
if (tab?.editor) {
|
||||
// Show query explanation
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const copyAsCteCommand = commands.registerCommand('query_tools.copy_as_cte', {
|
||||
execute: () => {
|
||||
const tab = sqlLab.getCurrentTab();
|
||||
if (tab?.editor) {
|
||||
// Copy selected text as CTE
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
context.subscriptions.push(formatCommand, explainCommand, copyAsCteCommand);
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Contribution Types](../contribution-types)** - Learn about other contribution types (commands, menus)
|
||||
- **[Development](../development)** - Set up your development environment
|
||||
- **[Quick Start](../quick-start)** - Build a complete extension
|
||||
459
docs/developer_portal/extensions/mcp.md
Normal file
459
docs/developer_portal/extensions/mcp.md
Normal file
@@ -0,0 +1,459 @@
|
||||
---
|
||||
title: MCP Integration
|
||||
hide_title: true
|
||||
sidebar_position: 7
|
||||
version: 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.
|
||||
-->
|
||||
|
||||
# MCP Integration
|
||||
|
||||
Model Context Protocol (MCP) integration allows extensions to register custom AI agent capabilities that integrate seamlessly with Superset's MCP service. Extensions can provide both **tools** (executable functions) and **prompts** (interactive guidance) that AI agents can discover and use.
|
||||
|
||||
## What is MCP?
|
||||
|
||||
MCP enables extensions to extend Superset's AI capabilities in two ways:
|
||||
|
||||
### MCP Tools
|
||||
Tools are Python functions that AI agents can call to perform specific tasks. They provide executable functionality that extends Superset's capabilities.
|
||||
|
||||
**Examples of MCP tools:**
|
||||
- Data processing and transformation functions
|
||||
- Custom analytics calculations
|
||||
- Integration with external APIs
|
||||
- Specialized report generation
|
||||
- Business-specific operations
|
||||
|
||||
### MCP Prompts
|
||||
Prompts provide interactive guidance and context to AI agents. They help agents understand how to better assist users with specific workflows or domain knowledge.
|
||||
|
||||
**Examples of MCP prompts:**
|
||||
- Step-by-step workflow guidance
|
||||
- Domain-specific context and knowledge
|
||||
- Interactive troubleshooting assistance
|
||||
- Template generation helpers
|
||||
- Best practices recommendations
|
||||
|
||||
## Getting Started
|
||||
|
||||
## MCP Tools
|
||||
|
||||
### Basic Tool Registration
|
||||
|
||||
The simplest way to create an MCP tool is using the `@tool` decorator:
|
||||
|
||||
```python
|
||||
from superset_core.mcp import tool
|
||||
|
||||
@tool
|
||||
def hello_world() -> dict:
|
||||
"""A simple greeting tool."""
|
||||
return {"message": "Hello from my extension!"}
|
||||
```
|
||||
|
||||
This creates a tool that AI agents can call by name. The tool name defaults to the function name.
|
||||
|
||||
### Decorator Parameters
|
||||
|
||||
The `@tool` decorator accepts several optional parameters:
|
||||
|
||||
**Parameter details:**
|
||||
- **`name`**: Tool identifier (AI agents use this to call your tool)
|
||||
- **`description`**: Explains what the tool does (helps AI agents decide when to use it)
|
||||
- **`tags`**: Categories for organization and discovery
|
||||
- **`protect`**: Whether the tool requires user authentication (defaults to `True`)
|
||||
|
||||
### Naming Your Tools
|
||||
|
||||
For extensions, include your extension ID in tool names to avoid conflicts:
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a more comprehensive example showing best practices:
|
||||
|
||||
```python
|
||||
# backend/mcp_tools.py
|
||||
import random
|
||||
from datetime import datetime, timezone
|
||||
from pydantic import BaseModel, Field
|
||||
from superset_core.mcp import tool
|
||||
|
||||
class RandomNumberRequest(BaseModel):
|
||||
"""Request schema for random number generation."""
|
||||
|
||||
min_value: int = Field(
|
||||
description="Minimum value (inclusive) for random number generation",
|
||||
ge=-2147483648,
|
||||
le=2147483647
|
||||
)
|
||||
max_value: int = Field(
|
||||
description="Maximum value (inclusive) for random number generation",
|
||||
ge=-2147483648,
|
||||
le=2147483647
|
||||
)
|
||||
|
||||
@tool(
|
||||
name="example_extension.random_number",
|
||||
tags=["extension", "utility", "random", "generator"]
|
||||
)
|
||||
def random_number_generator(request: RandomNumberRequest) -> dict:
|
||||
"""
|
||||
Generate a random integer between specified bounds.
|
||||
|
||||
This tool validates input ranges and provides detailed error messages
|
||||
for invalid requests.
|
||||
"""
|
||||
|
||||
# Validate business logic (Pydantic handles type/range validation)
|
||||
if request.min_value > request.max_value:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": f"min_value ({request.min_value}) cannot be greater than max_value ({request.max_value})",
|
||||
"timestamp": datetime.now(timezone.utc).isoformat()
|
||||
}
|
||||
|
||||
# Generate random number
|
||||
result = random.randint(request.min_value, request.max_value)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"random_number": result,
|
||||
"min_value": request.min_value,
|
||||
"max_value": request.max_value,
|
||||
"range_size": request.max_value - request.min_value + 1,
|
||||
"timestamp": datetime.now(timezone.utc).isoformat()
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Response Format
|
||||
|
||||
Use consistent response structures:
|
||||
|
||||
```python
|
||||
# Success response
|
||||
{
|
||||
"status": "success",
|
||||
"result": "your_data_here",
|
||||
"timestamp": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
|
||||
# Error response
|
||||
{
|
||||
"status": "error",
|
||||
"error": "Clear error message",
|
||||
"timestamp": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
Write clear descriptions and docstrings:
|
||||
|
||||
```python
|
||||
@tool(
|
||||
name="my_extension.process_data",
|
||||
description="Process customer data and generate insights. Requires valid customer ID and date range.",
|
||||
tags=["analytics", "customer", "reporting"]
|
||||
)
|
||||
def process_data(customer_id: int, start_date: str, end_date: str) -> dict:
|
||||
"""
|
||||
Process customer data for the specified date range.
|
||||
|
||||
This tool analyzes customer behavior patterns and generates
|
||||
actionable insights for business decision-making.
|
||||
|
||||
Args:
|
||||
customer_id: Unique customer identifier
|
||||
start_date: Analysis start date (YYYY-MM-DD format)
|
||||
end_date: Analysis end date (YYYY-MM-DD format)
|
||||
|
||||
Returns:
|
||||
Dictionary containing analysis results and recommendations
|
||||
"""
|
||||
# Implementation here
|
||||
pass
|
||||
```
|
||||
|
||||
### Tool Naming
|
||||
|
||||
- **Extension tools**: Use prefixed names like `my_extension.tool_name`
|
||||
- **Descriptive names**: `calculate_tax_amount` vs `calculate`
|
||||
- **Consistent naming**: Follow patterns within your extension
|
||||
|
||||
## How AI Agents Use Your Tools
|
||||
|
||||
Once registered, AI agents can discover and use your tools automatically:
|
||||
|
||||
```
|
||||
User: "Generate a random number between 1 and 100"
|
||||
Agent: I'll use the random number generator tool.
|
||||
→ Calls: example_extension.random_number(min_value=1, max_value=100)
|
||||
← Returns: {"status": "success", "random_number": 42, ...}
|
||||
Agent: I generated the number 42 for you.
|
||||
```
|
||||
|
||||
The AI agent sees your tool's:
|
||||
- **Name**: How to call it
|
||||
- **Description**: What it does and when to use it
|
||||
- **Parameters**: What inputs it expects (from Pydantic schema)
|
||||
- **Tags**: Categories for discovery
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Tool Not Available to AI Agents
|
||||
|
||||
1. **Check extension registration**: Verify your tool module is listed in extension entrypoints
|
||||
2. **Verify decorator**: Ensure `@tool` is correctly applied
|
||||
3. **Extension loading**: Confirm your extension is installed and enabled
|
||||
|
||||
### Input Validation Errors
|
||||
|
||||
1. **Pydantic models**: Ensure field types match expected inputs
|
||||
2. **Field constraints**: Check min/max values and string lengths are reasonable
|
||||
3. **Required fields**: Verify which parameters are required vs optional
|
||||
|
||||
### Runtime Issues
|
||||
|
||||
1. **Error handling**: Add try/catch blocks with clear error messages
|
||||
2. **Response format**: Use consistent status/error/timestamp structure
|
||||
3. **Testing**: Test your tools with various input scenarios
|
||||
|
||||
### Development Tips
|
||||
|
||||
1. **Start simple**: Begin with basic tools, add complexity gradually
|
||||
2. **Test locally**: Use MCP clients (like Claude Desktop) to test your tools
|
||||
3. **Clear descriptions**: Write tool descriptions as if explaining to a new user
|
||||
4. **Meaningful tags**: Use tags that help categorize and discover tools
|
||||
5. **Error messages**: Provide specific, actionable error messages
|
||||
|
||||
## MCP Prompts
|
||||
|
||||
### Basic Prompt Registration
|
||||
|
||||
Create interactive prompts using the `@prompt` decorator:
|
||||
|
||||
```python
|
||||
from superset_core.mcp import prompt
|
||||
from fastmcp import Context
|
||||
|
||||
@prompt("my_extension.workflow_guide")
|
||||
async def workflow_guide(ctx: Context) -> str:
|
||||
"""Interactive guide for data analysis workflows."""
|
||||
return """
|
||||
# Data Analysis Workflow Guide
|
||||
|
||||
Here's a step-by-step approach to effective data analysis in Superset:
|
||||
|
||||
## 1. Data Discovery
|
||||
- Start by exploring your datasets using the dataset browser
|
||||
- Check data quality and identify key metrics
|
||||
- Look for patterns and relationships in your data
|
||||
|
||||
## 2. Chart Creation
|
||||
- Choose appropriate visualizations for your data types
|
||||
- Apply filters to focus on relevant subsets
|
||||
- Configure proper aggregations and groupings
|
||||
|
||||
## 3. Dashboard Assembly
|
||||
- Combine related charts into coherent dashboards
|
||||
- Use filters and parameters for interactivity
|
||||
- Add markdown components for context and explanations
|
||||
|
||||
Would you like guidance on any specific step?
|
||||
"""
|
||||
```
|
||||
|
||||
### Advanced Prompt Examples
|
||||
|
||||
#### Domain-Specific Context
|
||||
|
||||
```python
|
||||
@prompt(
|
||||
"sales_extension.sales_analysis_guide",
|
||||
title="Sales Analysis Guide",
|
||||
description="Specialized guidance for sales data analysis workflows"
|
||||
)
|
||||
async def sales_analysis_guide(ctx: Context) -> str:
|
||||
"""Provides sales-specific analysis guidance and best practices."""
|
||||
return """
|
||||
# Sales Data Analysis Best Practices
|
||||
|
||||
## Key Metrics to Track
|
||||
- **Revenue Growth**: Month-over-month and year-over-year trends
|
||||
- **Conversion Rates**: Lead-to-opportunity-to-close ratios
|
||||
- **Customer Lifetime Value**: Total value per customer segment
|
||||
- **Sales Cycle Length**: Time from lead to close by product/region
|
||||
|
||||
## Recommended Chart Types
|
||||
- **Time Series**: Revenue trends, seasonal patterns
|
||||
- **Funnel Charts**: Conversion analysis across sales stages
|
||||
- **Geographic Maps**: Regional performance comparison
|
||||
- **Cohort Analysis**: Customer retention and growth patterns
|
||||
|
||||
## Common Pitfalls to Avoid
|
||||
- Don't mix different time granularities without proper context
|
||||
- Always normalize for business days when comparing periods
|
||||
- Consider external factors (holidays, market events) in analysis
|
||||
- Segment by relevant dimensions (product, region, channel)
|
||||
|
||||
## Next Steps
|
||||
1. Identify your primary sales KPIs
|
||||
2. Create baseline trend charts for each metric
|
||||
3. Build comparative views across segments
|
||||
4. Set up automated alerts for significant changes
|
||||
"""
|
||||
```
|
||||
|
||||
#### Interactive Troubleshooting
|
||||
|
||||
```python
|
||||
@prompt("support_extension.troubleshoot_charts")
|
||||
async def troubleshoot_charts(ctx: Context) -> str:
|
||||
"""Interactive troubleshooting assistant for chart issues."""
|
||||
return """
|
||||
# Chart Troubleshooting Assistant
|
||||
|
||||
Let's diagnose your chart issue step by step:
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### 🚫 No Data Showing
|
||||
**Possible causes:**
|
||||
- Filters are too restrictive
|
||||
- Date range doesn't match your data
|
||||
- Database connection issues
|
||||
- Missing permissions
|
||||
|
||||
**Check:** Try removing all filters and expanding the date range first.
|
||||
|
||||
### 📊 Unexpected Aggregation Results
|
||||
**Possible causes:**
|
||||
- Incorrect grouping dimensions
|
||||
- Wrong aggregation function (SUM vs COUNT vs AVG)
|
||||
- Data quality issues (duplicates, nulls)
|
||||
- Time zone mismatches
|
||||
|
||||
**Check:** Verify your GROUP BY columns and aggregation logic.
|
||||
|
||||
### 🐌 Slow Performance
|
||||
**Possible causes:**
|
||||
- Large dataset without proper indexing
|
||||
- Complex joins or calculations
|
||||
- Missing query optimizations
|
||||
- Resource constraints
|
||||
|
||||
**Check:** Simplify the query and add appropriate filters first.
|
||||
|
||||
## Debug Steps
|
||||
1. **Start Simple**: Create a basic count query first
|
||||
2. **Add Gradually**: Introduce complexity step by step
|
||||
3. **Check SQL**: Review the generated SQL for issues
|
||||
4. **Test Data**: Verify with a small sample first
|
||||
|
||||
What specific issue are you experiencing?
|
||||
"""
|
||||
```
|
||||
|
||||
### Prompt Best Practices
|
||||
|
||||
#### Content Structure
|
||||
- **Use clear headings** and sections for easy navigation
|
||||
- **Provide actionable steps** rather than just theory
|
||||
- **Include examples** relevant to the user's domain
|
||||
- **Offer next steps** to continue the workflow
|
||||
|
||||
#### Interactive Design
|
||||
- **Ask questions** to engage the user
|
||||
- **Provide options** for different scenarios
|
||||
- **Reference specific Superset features** by name
|
||||
- **Link to related tools** when appropriate
|
||||
|
||||
#### Context Awareness
|
||||
```python
|
||||
@prompt("analytics_extension.context_aware_guide")
|
||||
async def context_aware_guide(ctx: Context) -> str:
|
||||
"""Provides guidance based on current user context."""
|
||||
# Access user information if available
|
||||
user_info = getattr(ctx, 'user', None)
|
||||
|
||||
guidance = """# Personalized Analytics Guide\n\n"""
|
||||
|
||||
if user_info:
|
||||
guidance += f"Welcome back! Here's guidance tailored for your role:\n\n"
|
||||
|
||||
guidance += """
|
||||
## Getting Started
|
||||
Based on your previous activity, here are recommended next steps:
|
||||
|
||||
1. **Review Recent Dashboards**: Check your most-used dashboards for updates
|
||||
2. **Explore New Data**: Look for recently added datasets in your domain
|
||||
3. **Share Insights**: Consider sharing successful analyses with your team
|
||||
|
||||
## Advanced Techniques
|
||||
- Set up automated alerts for key metrics
|
||||
- Create parameterized dashboards for different audiences
|
||||
- Use SQL Lab for complex custom analyses
|
||||
"""
|
||||
|
||||
return guidance
|
||||
```
|
||||
|
||||
## Combining Tools and Prompts
|
||||
|
||||
Extensions can provide both tools and prompts that work together:
|
||||
|
||||
```python
|
||||
# Tool for data processing
|
||||
@tool("analytics_extension.calculate_metrics")
|
||||
def calculate_metrics(data: dict) -> dict:
|
||||
"""Calculate advanced analytics metrics."""
|
||||
# Implementation here
|
||||
pass
|
||||
|
||||
# Prompt that guides users to the tool
|
||||
@prompt("analytics_extension.metrics_guide")
|
||||
async def metrics_guide(ctx: Context) -> str:
|
||||
"""Guide users through advanced metrics calculation."""
|
||||
return """
|
||||
# Advanced Metrics Calculation
|
||||
|
||||
Use the `calculate_metrics` tool to compute specialized analytics:
|
||||
|
||||
## Available Metrics
|
||||
- Customer Lifetime Value (CLV)
|
||||
- Cohort Retention Rates
|
||||
- Statistical Significance Tests
|
||||
- Predictive Trend Analysis
|
||||
|
||||
## Usage
|
||||
Call the tool with your dataset to get detailed calculations
|
||||
and recommendations for visualization approaches.
|
||||
|
||||
Would you like to calculate metrics for your current dataset?
|
||||
"""
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Development](./development)** - Project structure, APIs, and dev workflow
|
||||
- **[Security](./security)** - Security best practices for extensions
|
||||
53
docs/developer_portal/extensions/overview.md
Normal file
53
docs/developer_portal/extensions/overview.md
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: 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.
|
||||
-->
|
||||
|
||||
# Overview
|
||||
|
||||
Apache Superset's extension system enables organizations to build custom features without modifying the core codebase. Inspired by the [VS Code extension model](https://code.visualstudio.com/api), this architecture addresses a long-standing challenge: teams previously had to fork Superset or make invasive modifications to add capabilities like query optimizers, custom panels, or specialized integrations—resulting in maintenance overhead and codebase fragmentation.
|
||||
|
||||
The extension system introduces a modular, plugin-based architecture where both built-in features and external extensions use the same well-defined APIs. This "lean core" approach ensures that any capability available to Superset's internal features is equally accessible to community-developed extensions, fostering a vibrant ecosystem while reducing the maintenance burden on core contributors.
|
||||
|
||||
## What are Superset Extensions?
|
||||
|
||||
Superset extensions are self-contained `.supx` packages that extend the platform's capabilities through standardized contribution points. Each extension can include both frontend (React/TypeScript) and backend (Python) components, bundled together and loaded dynamically at runtime using Webpack Module Federation.
|
||||
|
||||
## Extension Capabilities
|
||||
|
||||
Extensions can provide:
|
||||
|
||||
- **Custom UI Components**: New panels, views, and interactive elements
|
||||
- **Commands and Menus**: Custom actions accessible via menus and keyboard shortcuts
|
||||
- **REST API Endpoints**: Backend services under the `/api/v1/extensions/` namespace
|
||||
- **MCP Tools and Prompts**: AI agent capabilities for enhanced user assistance
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Quick Start](./quick-start)** - Build your first extension with a complete walkthrough
|
||||
- **[Architecture](./architecture)** - Design principles and system overview
|
||||
- **[Contribution Types](./contribution-types)** - Available extension points
|
||||
- **[Development](./development)** - Project structure, APIs, and development workflow
|
||||
- **[Deployment](./deployment)** - Packaging and deploying extensions
|
||||
- **[MCP Integration](./mcp)** - Adding AI agent capabilities using extensions
|
||||
- **[Security](./security)** - Security considerations and best practices
|
||||
- **[Community Extensions](./registry)** - Browse extensions shared by the community
|
||||
514
docs/developer_portal/extensions/quick-start.md
Normal file
514
docs/developer_portal/extensions/quick-start.md
Normal file
@@ -0,0 +1,514 @@
|
||||
---
|
||||
title: Quick Start
|
||||
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.
|
||||
-->
|
||||
|
||||
# Quick Start
|
||||
|
||||
This guide walks you through creating your first Superset extension - a simple "Hello World" panel that displays a message fetched from a backend API endpoint. You'll learn the essential structure and patterns for building full-stack Superset extensions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting, ensure you have:
|
||||
|
||||
- Node.js and npm compatible with your Superset version
|
||||
- Python compatible with your Superset version
|
||||
- A running Superset development environment
|
||||
- Basic knowledge of React, TypeScript, and Flask
|
||||
|
||||
## Step 1: Install the Extensions CLI
|
||||
|
||||
First, install the Apache Superset Extensions CLI:
|
||||
|
||||
```bash
|
||||
pip install apache-superset-extensions-cli
|
||||
```
|
||||
|
||||
## Step 2: Create a New Extension
|
||||
|
||||
Use the CLI to scaffold a new extension project. Extensions can include frontend functionality, backend functionality, or both, depending on your needs. This quickstart demonstrates a full-stack extension with both frontend UI components and backend API endpoints to show the complete integration pattern.
|
||||
|
||||
```bash
|
||||
superset-extensions init
|
||||
```
|
||||
|
||||
The CLI will prompt you for information:
|
||||
|
||||
```
|
||||
Extension ID (unique identifier, alphanumeric only): hello_world
|
||||
Extension name (human-readable display name): Hello World
|
||||
Initial version [0.1.0]: 0.1.0
|
||||
License [Apache-2.0]: Apache-2.0
|
||||
Include frontend? [Y/n]: Y
|
||||
Include backend? [Y/n]: Y
|
||||
```
|
||||
|
||||
This creates a complete project structure:
|
||||
|
||||
```
|
||||
hello_world/
|
||||
├── extension.json # Extension metadata and configuration
|
||||
├── backend/ # Backend Python code
|
||||
│ ├── src/
|
||||
│ │ └── hello_world/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── entrypoint.py # Backend registration
|
||||
│ └── pyproject.toml
|
||||
└── frontend/ # Frontend TypeScript/React code
|
||||
├── src/
|
||||
│ └── index.tsx # Frontend entry point
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── webpack.config.js
|
||||
```
|
||||
|
||||
## Step 3: Configure Extension Metadata
|
||||
|
||||
The generated `extension.json` contains basic metadata. Update it to register your panel in SQL Lab:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "hello_world",
|
||||
"name": "Hello World",
|
||||
"version": "0.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab.panels": [
|
||||
{
|
||||
"id": "hello_world.main",
|
||||
"name": "Hello World"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"moduleFederation": {
|
||||
"exposes": ["./index"]
|
||||
}
|
||||
},
|
||||
"backend": {
|
||||
"entryPoints": ["hello_world.entrypoint"],
|
||||
"files": ["backend/src/hello_world/**/*.py"]
|
||||
},
|
||||
"permissions": ["can_read"]
|
||||
}
|
||||
```
|
||||
|
||||
**Key fields:**
|
||||
|
||||
- `frontend.contributions.views.sqllab.panels`: Registers your panel in SQL Lab
|
||||
- `backend.entryPoints`: Python modules to load eagerly when extension starts
|
||||
|
||||
## Step 4: Create Backend API
|
||||
|
||||
The CLI generated a basic `backend/src/hello_world/entrypoint.py`. We'll create an API endpoint.
|
||||
|
||||
**Create `backend/src/hello_world/api.py`**
|
||||
|
||||
```python
|
||||
from flask import Response
|
||||
from flask_appbuilder.api import expose, protect, safe
|
||||
from superset_core.api.rest_api import RestApi
|
||||
|
||||
|
||||
class HelloWorldAPI(RestApi):
|
||||
resource_name = "hello_world"
|
||||
openapi_spec_tag = "Hello World"
|
||||
class_permission_name = "hello_world"
|
||||
|
||||
@expose("/message", methods=("GET",))
|
||||
@protect()
|
||||
@safe
|
||||
def get_message(self) -> Response:
|
||||
"""Gets a hello world message
|
||||
---
|
||||
get:
|
||||
description: >-
|
||||
Get a hello world message from the backend
|
||||
responses:
|
||||
200:
|
||||
description: Hello world message
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
result:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
"""
|
||||
return self.response(
|
||||
200,
|
||||
result={"message": "Hello from the backend!"}
|
||||
)
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
|
||||
- Extends `RestApi` from `superset_core.api.types.rest_api`
|
||||
- Uses Flask-AppBuilder decorators (`@expose`, `@protect`, `@safe`)
|
||||
- Returns responses using `self.response(status_code, result=data)`
|
||||
- The endpoint will be accessible at `/extensions/hello_world/message`
|
||||
- OpenAPI docstrings are crucial - Flask-AppBuilder uses them to automatically generate interactive API documentation at `/swagger/v1`, allowing developers to explore endpoints, understand schemas, and test the API directly from the browser
|
||||
|
||||
**Update `backend/src/hello_world/entrypoint.py`**
|
||||
|
||||
Replace the generated print statement with API registration:
|
||||
|
||||
```python
|
||||
from superset_core.api import rest_api
|
||||
|
||||
from .api import HelloWorldAPI
|
||||
|
||||
rest_api.add_extension_api(HelloWorldAPI)
|
||||
```
|
||||
|
||||
This registers your API with Superset when the extension loads.
|
||||
|
||||
## Step 5: Create Frontend Component
|
||||
|
||||
The CLI generates the frontend configuration files. Below are the key configurations that enable Module Federation integration with Superset.
|
||||
|
||||
**`frontend/package.json`**
|
||||
|
||||
The `@apache-superset/core` package must be listed in both `peerDependencies` (to declare runtime compatibility) and `devDependencies` (to provide TypeScript types during build):
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "hello_world",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"start": "webpack serve --mode development",
|
||||
"build": "webpack --stats-error-details --mode production"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "^x.x.x",
|
||||
"react": "^x.x.x",
|
||||
"react-dom": "^x.x.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apache-superset/core": "^x.x.x",
|
||||
"@types/react": "^x.x.x",
|
||||
"ts-loader": "^x.x.x",
|
||||
"typescript": "^x.x.x",
|
||||
"webpack": "^5.x.x",
|
||||
"webpack-cli": "^x.x.x",
|
||||
"webpack-dev-server": "^x.x.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**`frontend/webpack.config.js`**
|
||||
|
||||
The webpack configuration requires specific settings for Module Federation. Key settings include `externalsType: "window"` and `externals` to map `@apache-superset/core` to `window.superset` at runtime, `import: false` for shared modules to use the host's React instead of bundling a separate copy, and `remoteEntry.[contenthash].js` for cache busting:
|
||||
|
||||
```javascript
|
||||
const path = require("path");
|
||||
const { ModuleFederationPlugin } = require("webpack").container;
|
||||
const packageConfig = require("./package.json");
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const isProd = argv.mode === "production";
|
||||
|
||||
return {
|
||||
entry: isProd ? {} : "./src/index.tsx",
|
||||
mode: isProd ? "production" : "development",
|
||||
devServer: {
|
||||
port: 3001,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
filename: isProd ? undefined : "[name].[contenthash].js",
|
||||
chunkFilename: "[name].[contenthash].js",
|
||||
clean: true,
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
publicPath: `/api/v1/extensions/${packageConfig.name}/`,
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
||||
},
|
||||
// Map @apache-superset/core imports to window.superset at runtime
|
||||
externalsType: "window",
|
||||
externals: {
|
||||
"@apache-superset/core": "superset",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: "ts-loader",
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new ModuleFederationPlugin({
|
||||
name: packageConfig.name,
|
||||
filename: "remoteEntry.[contenthash].js",
|
||||
exposes: {
|
||||
"./index": "./src/index.tsx",
|
||||
},
|
||||
shared: {
|
||||
react: {
|
||||
singleton: true,
|
||||
requiredVersion: packageConfig.peerDependencies.react,
|
||||
import: false, // Use host's React, don't bundle
|
||||
},
|
||||
"react-dom": {
|
||||
singleton: true,
|
||||
requiredVersion: packageConfig.peerDependencies["react-dom"],
|
||||
import: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
**`frontend/tsconfig.json`**
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
```
|
||||
|
||||
**Create `frontend/src/HelloWorldPanel.tsx`**
|
||||
|
||||
Create a new file for the component implementation:
|
||||
|
||||
```tsx
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { authentication } from '@apache-superset/core';
|
||||
|
||||
const HelloWorldPanel: React.FC = () => {
|
||||
const [message, setMessage] = useState<string>('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMessage = async () => {
|
||||
try {
|
||||
const csrfToken = await authentication.getCSRFToken();
|
||||
const response = await fetch('/extensions/hello_world/message', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken!,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server returned ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setMessage(data.result.message);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An error occurred');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMessage();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ padding: '20px', textAlign: 'center' }}>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div style={{ padding: '20px', color: 'red' }}>
|
||||
<strong>Error:</strong> {error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<h3>Hello World Extension</h3>
|
||||
<div
|
||||
style={{
|
||||
padding: '16px',
|
||||
backgroundColor: '#f6ffed',
|
||||
border: '1px solid #b7eb8f',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '16px',
|
||||
}}
|
||||
>
|
||||
<strong>{message}</strong>
|
||||
</div>
|
||||
<p>This message was fetched from the backend API! 🎉</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HelloWorldPanel;
|
||||
```
|
||||
|
||||
**Update `frontend/src/index.tsx`**
|
||||
|
||||
Replace the generated code with the extension entry point:
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { core } from '@apache-superset/core';
|
||||
import HelloWorldPanel from './HelloWorldPanel';
|
||||
|
||||
export const activate = (context: core.ExtensionContext) => {
|
||||
context.disposables.push(
|
||||
core.registerViewProvider('hello_world.main', () => <HelloWorldPanel />),
|
||||
);
|
||||
};
|
||||
|
||||
export const deactivate = () => {};
|
||||
```
|
||||
|
||||
**Key patterns:**
|
||||
|
||||
- `activate` function is called when the extension loads
|
||||
- `core.registerViewProvider` registers the component with ID `hello_world.main` (matching `extension.json`)
|
||||
- `authentication.getCSRFToken()` retrieves the CSRF token for API calls
|
||||
- Fetch calls to `/extensions/{extension_id}/{endpoint}` reach your backend API
|
||||
- `context.disposables.push()` ensures proper cleanup
|
||||
|
||||
## Step 6: Install Dependencies
|
||||
|
||||
Install the frontend dependencies:
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
cd ..
|
||||
```
|
||||
|
||||
## Step 7: Package the Extension
|
||||
|
||||
Create a `.supx` bundle for deployment:
|
||||
|
||||
```bash
|
||||
superset-extensions bundle
|
||||
```
|
||||
|
||||
This command automatically:
|
||||
|
||||
- Builds frontend assets using Webpack with Module Federation
|
||||
- Collects backend Python source files
|
||||
- Creates a `dist/` directory with:
|
||||
- `manifest.json` - Build metadata and asset references
|
||||
- `frontend/dist/` - Built frontend assets (remoteEntry.js, chunks)
|
||||
- `backend/` - Python source files
|
||||
- Packages everything into `hello_world-0.1.0.supx` - a zip archive with the specific structure required by Superset
|
||||
|
||||
## Step 8: Deploy to Superset
|
||||
|
||||
To deploy your extension, you need to enable extensions support and configure where Superset should load them from.
|
||||
|
||||
**Configure Superset**
|
||||
|
||||
Add the following to your `superset_config.py`:
|
||||
|
||||
```python
|
||||
# Enable extensions feature
|
||||
FEATURE_FLAGS = {
|
||||
"EXTENSIONS": True,
|
||||
}
|
||||
|
||||
# Set the directory where extensions are stored
|
||||
EXTENSIONS_PATH = "/path/to/extensions/folder"
|
||||
```
|
||||
|
||||
**Copy Extension Bundle**
|
||||
|
||||
Copy your `.supx` file to the configured extensions path:
|
||||
|
||||
```bash
|
||||
cp hello_world-0.1.0.supx /path/to/extensions/folder/
|
||||
```
|
||||
|
||||
**Restart Superset**
|
||||
|
||||
Restart your Superset instance to load the extension:
|
||||
|
||||
```bash
|
||||
# Restart your Superset server
|
||||
superset run
|
||||
```
|
||||
|
||||
Superset will extract and validate the extension metadata, load the assets, register the extension with its capabilities, and make it available for use.
|
||||
|
||||
## Step 9: Test Your Extension
|
||||
|
||||
1. **Open SQL Lab** in Superset
|
||||
2. Look for the **"Hello World"** panel in the panels dropdown or sidebar
|
||||
3. Open the panel - it should display "Hello from the backend!"
|
||||
4. Check that the message was fetched from your API endpoint
|
||||
|
||||
## Understanding the Flow
|
||||
|
||||
Here's what happens when your extension loads:
|
||||
|
||||
1. **Superset starts**: Reads `extension.json` and loads backend entrypoint
|
||||
2. **Backend registration**: `entrypoint.py` registers your API via `rest_api.add_extension_api()`
|
||||
3. **Frontend loads**: When SQL Lab opens, Superset fetches the remote entry file
|
||||
4. **Module Federation**: Webpack loads your extension code and resolves `@apache-superset/core` to `window.superset`
|
||||
5. **Activation**: `activate()` is called, registering your view provider
|
||||
6. **Rendering**: When the user opens your panel, React renders `<HelloWorldPanel />`
|
||||
7. **API call**: Component fetches data from `/extensions/hello_world/message`
|
||||
8. **Backend response**: Your Flask API returns the hello world message
|
||||
9. **Display**: Component shows the message to the user
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you have a working extension, explore:
|
||||
|
||||
- **[Development](./development)** - Project structure, APIs, and development workflow
|
||||
- **[Contribution Types](./contribution-types)** - Other contribution points beyond panels
|
||||
- **[Deployment](./deployment)** - Packaging and deploying your extension
|
||||
- **[Security](./security)** - Security best practices for extensions
|
||||
|
||||
For a complete real-world example, examine the query insights extension in the Superset codebase.
|
||||
47
docs/developer_portal/extensions/registry.md
Normal file
47
docs/developer_portal/extensions/registry.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Community Extensions
|
||||
sidebar_position: 9
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Community Extensions
|
||||
|
||||
This page serves as a registry of community-created Superset extensions. These extensions are developed and maintained by community members and are not officially supported or vetted by the Apache Superset project. **Before installing any community extension, administrators are responsible for evaluating the extension's source code for security vulnerabilities, performance impact, UI/UX quality, and compatibility with their Superset deployment.**
|
||||
|
||||
## Extensions
|
||||
|
||||
| Name | Description | Author | Preview |
|
||||
| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Extensions API Explorer](https://github.com/michael-s-molina/superset-extensions/tree/main/api_explorer) | A SQL Lab panel that demonstrates the Extensions API by providing an interactive explorer for testing commands like getTabs, getCurrentTab, and getDatabases. Useful for extension developers to understand and experiment with the available APIs. | Michael S. Molina | <a href="/img/extensions/api_explorer.png" target="_blank"><img src="/img/extensions/api_explorer.png" alt="Extensions API Explorer" width="120" /></a> |
|
||||
| [SQL Query Flow Visualizer](https://github.com/msyavuz/superset-sql-visualizer) | A SQL Lab panel that transforms SQL queries into interactive flow diagrams, helping developers and analysts understand query execution paths and data relationships.| Mehmet Salih Yavuz | <a href="/img/extensions/sql_flow_visualizer.png" target="_blank"><img src="/img/extensions/sql_flow_visualizer.png" alt="SQL Flow Visualizer" width="120" /></a> |
|
||||
## How to Add Your Extension
|
||||
|
||||
To add your extension to this registry, submit a pull request to the [Apache Superset repository](https://github.com/apache/superset) with the following changes:
|
||||
|
||||
1. Add a row to the **Extensions** table above using this format:
|
||||
|
||||
```markdown
|
||||
| [Your Extension](https://github.com/your-username/your-repo) | A brief description of your extension. | Your Name | <a href="/img/extensions/your-screenshot.png" target="_blank"><img src="/img/extensions/your-screenshot.png" alt="Your Extension" width="120" /></a> |
|
||||
```
|
||||
|
||||
2. Add a screenshot to `docs/static/img/extensions/` (recommended size: 800x450px, PNG or JPG format)
|
||||
|
||||
3. Submit your PR with a title like "docs: Add [Extension Name] to community extensions registry"
|
||||
35
docs/developer_portal/extensions/security.md
Normal file
35
docs/developer_portal/extensions/security.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Security
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Security
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.**
|
||||
|
||||
Any security concerns regarding built-in extensions (included in Superset's monorepo) should be reported to the Superset Security mailing list for triage and resolution by maintainers.
|
||||
@@ -1,248 +0,0 @@
|
||||
---
|
||||
title: Getting Started
|
||||
sidebar_position: 2
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Getting Started with Extensions
|
||||
|
||||
This guide will walk you through creating, developing, and deploying your first Superset extension.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, make sure you have:
|
||||
|
||||
- **Node.js 16+** and npm/yarn installed
|
||||
- **Python 3.9+** and pip installed
|
||||
- **Superset** running locally or access to a Superset instance
|
||||
- **Git** for version control
|
||||
- Basic knowledge of React and TypeScript
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Install the CLI
|
||||
|
||||
First, install the Superset Extensions CLI globally:
|
||||
|
||||
```bash
|
||||
pip install apache-superset-extensions-cli
|
||||
```
|
||||
|
||||
### 2. Create Your First Extension
|
||||
|
||||
Use the CLI to scaffold a new extension project:
|
||||
|
||||
```bash
|
||||
superset-extensions init my-first-extension
|
||||
cd my-first-extension
|
||||
```
|
||||
|
||||
This creates the following structure:
|
||||
|
||||
```
|
||||
my-first-extension/
|
||||
├── extension.json # Extension metadata
|
||||
├── frontend/ # Frontend code
|
||||
│ ├── src/
|
||||
│ │ └── index.tsx # Main entry point
|
||||
│ ├── package.json
|
||||
│ └── webpack.config.js
|
||||
├── backend/ # Backend code (optional)
|
||||
│ ├── src/
|
||||
│ └── requirements.txt
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### 3. Configure Your Extension
|
||||
|
||||
Edit `extension.json` to define your extension's capabilities:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-first-extension",
|
||||
"version": "1.0.0",
|
||||
"description": "My first Superset extension",
|
||||
"author": "Your Name",
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab.panels": [
|
||||
{
|
||||
"id": "my-extension.main",
|
||||
"name": "My Panel",
|
||||
"icon": "ToolOutlined"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Develop Your Extension
|
||||
|
||||
Edit `frontend/src/index.tsx` to implement your extension:
|
||||
|
||||
```typescript
|
||||
import React from 'react';
|
||||
import { ExtensionContext } from '@apache-superset/core';
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
// Register your panel component
|
||||
const panel = context.core.registerView('my-extension.main', () => (
|
||||
<div style={{ padding: 20 }}>
|
||||
<h2>Hello from My Extension!</h2>
|
||||
<p>This is my first Superset extension.</p>
|
||||
</div>
|
||||
));
|
||||
|
||||
// Clean up on deactivation
|
||||
context.subscriptions.push(panel);
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
// Cleanup code if needed
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Test Locally
|
||||
|
||||
Enable development mode in your Superset configuration:
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
ENABLE_EXTENSIONS = True
|
||||
LOCAL_EXTENSIONS = [
|
||||
"/path/to/my-first-extension"
|
||||
]
|
||||
```
|
||||
|
||||
Run the development server:
|
||||
|
||||
```bash
|
||||
# In your extension directory
|
||||
superset-extensions dev
|
||||
|
||||
# In a separate terminal, start Superset
|
||||
superset run -p 8088 --with-threads --reload
|
||||
```
|
||||
|
||||
Your extension will now appear in SQL Lab!
|
||||
|
||||
### 6. Build and Package
|
||||
|
||||
When ready to distribute your extension:
|
||||
|
||||
```bash
|
||||
superset-extensions build
|
||||
superset-extensions bundle
|
||||
```
|
||||
|
||||
This creates a `my-first-extension-1.0.0.supx` file that can be uploaded to any Superset instance.
|
||||
|
||||
### 7. Deploy
|
||||
|
||||
Upload your extension via the Superset UI or API:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8088/api/v1/extensions/import/ \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-F "file=@my-first-extension-1.0.0.supx"
|
||||
```
|
||||
|
||||
## What's Next?
|
||||
|
||||
Now that you have a basic extension working, explore:
|
||||
|
||||
- [Extension Architecture](../architecture/overview) - Understand how extensions work
|
||||
- [API Reference](../api/frontend) - Learn about available APIs
|
||||
- [Examples](../examples) - See more complex extension examples
|
||||
- [Best Practices](./best-practices) - Learn extension development best practices
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Adding a SQL Lab Panel
|
||||
|
||||
```typescript
|
||||
const panel = context.core.registerView('my-extension.panel', () => (
|
||||
<MyPanelComponent />
|
||||
));
|
||||
```
|
||||
|
||||
### Registering Commands
|
||||
|
||||
```typescript
|
||||
const command = context.commands.registerCommand('my-extension.run', {
|
||||
title: 'Run My Command',
|
||||
execute: () => {
|
||||
// Command logic
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Listening to Events
|
||||
|
||||
```typescript
|
||||
const listener = context.sqlLab.onDidQueryRun((query) => {
|
||||
console.log('Query executed:', query);
|
||||
});
|
||||
```
|
||||
|
||||
### Adding Menu Items
|
||||
|
||||
```json
|
||||
{
|
||||
"menus": {
|
||||
"sqllab.editor": {
|
||||
"primary": [{
|
||||
"command": "my-extension.run",
|
||||
"when": "editorHasSelection"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Extension Not Loading
|
||||
|
||||
- Ensure `ENABLE_EXTENSIONS = True` in your config
|
||||
- Check the browser console for errors
|
||||
- Verify the extension path in `LOCAL_EXTENSIONS`
|
||||
|
||||
### Module Not Found Errors
|
||||
|
||||
- Run `npm install` in the frontend directory
|
||||
- Check that `@apache-superset/core` is properly externalized
|
||||
|
||||
### Build Failures
|
||||
|
||||
- Ensure all dependencies are installed
|
||||
- Check webpack.config.js for proper Module Federation setup
|
||||
- Verify extension.json is valid JSON
|
||||
|
||||
## Get Help
|
||||
|
||||
- Join the [Superset Slack](https://join.slack.com/t/apache-superset/shared_invite/zt-16jvzmoi8-sI1TY1Pm~y_RnSiUAN0jqQ)
|
||||
- Ask questions on [GitHub Discussions](https://github.com/apache/superset/discussions)
|
||||
- Report issues on [GitHub Issues](https://github.com/apache/superset/issues)
|
||||
318
docs/developer_portal/guidelines/backend-style-guidelines.md
Normal file
318
docs/developer_portal/guidelines/backend-style-guidelines.md
Normal file
@@ -0,0 +1,318 @@
|
||||
---
|
||||
title: Backend Style Guidelines
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Backend Style Guidelines
|
||||
|
||||
This is a list of statements that describe how we do backend development in Superset. While they might not be 100% true for all files in the repo, they represent the gold standard we strive towards for backend quality and style.
|
||||
|
||||
* We use a monolithic Python/Flask/Flask-AppBuilder backend, with small single-responsibility satellite services where necessary.
|
||||
* Files are generally organized by feature or object type. Within each domain, we can have api controllers, models, schemas, commands, and data access objects (dao).
|
||||
* See: [Proposal for Improving Superset's Python Code Organization](https://github.com/apache/superset/issues/9077)
|
||||
* API controllers use Marshmallow Schemas to serialize/deserialize data.
|
||||
* Authentication and authorization are controlled by the [security manager](https://github.com/apache/superset/blob/master/superset/security/manager).
|
||||
* We use Pytest for unit and integration tests. These live in the `tests` directory.
|
||||
* We add tests for every new piece of functionality added to the backend.
|
||||
* We use pytest fixtures to share setup between tests.
|
||||
* We use sqlalchemy to access both Superset's application database, and users' analytics databases.
|
||||
* We make changes backwards compatible whenever possible.
|
||||
* If a change cannot be made backwards compatible, it goes into a major release.
|
||||
* See: [Proposal For Semantic Versioning](https://github.com/apache/superset/issues/12566)
|
||||
* We use Swagger for API documentation, with docs written inline on the API endpoint code.
|
||||
* We prefer thin ORM models, putting shared functionality in other utilities.
|
||||
* Several linters/checkers are used to maintain consistent code style and type safety: pylint, pypy, black, isort.
|
||||
* `__init__.py` files are kept empty to avoid implicit dependencies.
|
||||
|
||||
## Code Organization
|
||||
|
||||
### Domain-Driven Structure
|
||||
|
||||
Organize code by business domain rather than technical layers:
|
||||
|
||||
```
|
||||
superset/
|
||||
├── dashboards/
|
||||
│ ├── api.py # API endpoints
|
||||
│ ├── commands/ # Business logic
|
||||
│ ├── dao.py # Data access layer
|
||||
│ ├── models.py # Database models
|
||||
│ └── schemas.py # Serialization schemas
|
||||
├── charts/
|
||||
│ ├── api.py
|
||||
│ ├── commands/
|
||||
│ ├── dao.py
|
||||
│ ├── models.py
|
||||
│ └── schemas.py
|
||||
```
|
||||
|
||||
### API Controllers
|
||||
|
||||
Use Flask-AppBuilder with Marshmallow schemas:
|
||||
|
||||
```python
|
||||
from flask_appbuilder.api import BaseApi
|
||||
from marshmallow import Schema, fields
|
||||
|
||||
class DashboardSchema(Schema):
|
||||
id = fields.Integer()
|
||||
dashboard_title = fields.String(required=True)
|
||||
created_on = fields.DateTime(dump_only=True)
|
||||
|
||||
class DashboardApi(BaseApi):
|
||||
resource_name = "dashboard"
|
||||
openapi_spec_tag = "Dashboards"
|
||||
|
||||
@expose("/", methods=["GET"])
|
||||
@protect()
|
||||
@safe
|
||||
def get_list(self) -> Response:
|
||||
"""Get a list of dashboards"""
|
||||
# Implementation
|
||||
```
|
||||
|
||||
### Commands Pattern
|
||||
|
||||
Use commands for business logic:
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
from superset.commands.base import BaseCommand
|
||||
from superset.dashboards.dao import DashboardDAO
|
||||
|
||||
class CreateDashboardCommand(BaseCommand):
|
||||
def __init__(self, properties: Dict[str, Any]):
|
||||
self._properties = properties
|
||||
|
||||
def run(self) -> Dashboard:
|
||||
self.validate()
|
||||
return DashboardDAO.create(self._properties)
|
||||
|
||||
def validate(self) -> None:
|
||||
if not self._properties.get("dashboard_title"):
|
||||
raise ValidationError("Title is required")
|
||||
```
|
||||
|
||||
### Data Access Objects (DAOs)
|
||||
|
||||
See: [DAO Style Guidelines and Best Practices](./backend/dao-style-guidelines)
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Tests
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
from superset.dashboards.commands.create import CreateDashboardCommand
|
||||
|
||||
def test_create_dashboard_success():
|
||||
properties = {
|
||||
"dashboard_title": "Test Dashboard",
|
||||
"owners": [1]
|
||||
}
|
||||
|
||||
command = CreateDashboardCommand(properties)
|
||||
dashboard = command.run()
|
||||
|
||||
assert dashboard.dashboard_title == "Test Dashboard"
|
||||
|
||||
def test_create_dashboard_validation_error():
|
||||
properties = {} # Missing required title
|
||||
|
||||
command = CreateDashboardCommand(properties)
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
command.run()
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from superset.app import create_app
|
||||
from superset.extensions import db
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = create_app()
|
||||
app.config["TESTING"] = True
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
yield app
|
||||
db.drop_all()
|
||||
|
||||
def test_dashboard_api_create(app, auth_headers):
|
||||
with app.test_client() as client:
|
||||
response = client.post(
|
||||
"/api/v1/dashboard/",
|
||||
json={"dashboard_title": "Test Dashboard"},
|
||||
headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 201
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
### Authorization Decorators
|
||||
|
||||
```python
|
||||
from flask_appbuilder.security.decorators import has_access
|
||||
|
||||
class DashboardApi(BaseApi):
|
||||
@expose("/", methods=["POST"])
|
||||
@protect()
|
||||
@has_access("can_write", "Dashboard")
|
||||
def post(self) -> Response:
|
||||
"""Create a new dashboard"""
|
||||
# Implementation
|
||||
```
|
||||
|
||||
### Input Validation
|
||||
|
||||
```python
|
||||
from marshmallow import Schema, fields, validate
|
||||
|
||||
class DashboardSchema(Schema):
|
||||
dashboard_title = fields.String(
|
||||
required=True,
|
||||
validate=validate.Length(min=1, max=500)
|
||||
)
|
||||
slug = fields.String(
|
||||
validate=validate.Regexp(r'^[a-z0-9-]+$')
|
||||
)
|
||||
```
|
||||
|
||||
## Database Operations
|
||||
|
||||
### Use SQLAlchemy ORM
|
||||
|
||||
```python
|
||||
from sqlalchemy import Column, Integer, String, Text
|
||||
from superset.models.helpers import AuditMixinNullable
|
||||
|
||||
class Dashboard(Model, AuditMixinNullable):
|
||||
__tablename__ = "dashboards"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
dashboard_title = Column(String(500))
|
||||
position_json = Column(Text)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Dashboard {self.dashboard_title}>"
|
||||
```
|
||||
|
||||
### Database Migrations
|
||||
|
||||
```python
|
||||
# migration file
|
||||
def upgrade():
|
||||
op.add_column(
|
||||
"dashboards",
|
||||
sa.Column("new_column", sa.String(255), nullable=True)
|
||||
)
|
||||
|
||||
def downgrade():
|
||||
op.drop_column("dashboards", "new_column")
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Custom Exceptions
|
||||
|
||||
```python
|
||||
class SupersetException(Exception):
|
||||
"""Base exception for Superset"""
|
||||
pass
|
||||
|
||||
class ValidationError(SupersetException):
|
||||
"""Raised when validation fails"""
|
||||
pass
|
||||
|
||||
class SecurityException(SupersetException):
|
||||
"""Raised when security check fails"""
|
||||
pass
|
||||
```
|
||||
|
||||
### Error Responses
|
||||
|
||||
```python
|
||||
from flask import jsonify
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
@app.errorhandler(ValidationError)
|
||||
def handle_validation_error(error):
|
||||
return jsonify({
|
||||
"message": str(error),
|
||||
"error_type": "VALIDATION_ERROR"
|
||||
}), 400
|
||||
```
|
||||
|
||||
## Type Hints and Documentation
|
||||
|
||||
### Use Type Hints
|
||||
|
||||
```python
|
||||
from typing import List, Optional, Dict, Any
|
||||
from superset.models.dashboard import Dashboard
|
||||
|
||||
def get_dashboards_by_owner(owner_id: int) -> List[Dashboard]:
|
||||
"""Get all dashboards owned by a specific user"""
|
||||
return db.session.query(Dashboard).filter_by(owner_id=owner_id).all()
|
||||
|
||||
def create_dashboard(properties: Dict[str, Any]) -> Optional[Dashboard]:
|
||||
"""Create a new dashboard with the given properties"""
|
||||
# Implementation
|
||||
```
|
||||
|
||||
### API Documentation
|
||||
|
||||
```python
|
||||
from flask_appbuilder.api import BaseApi
|
||||
from flask_appbuilder.api.schemas import get_list_schema
|
||||
|
||||
class DashboardApi(BaseApi):
|
||||
@expose("/", methods=["GET"])
|
||||
@protect()
|
||||
@safe
|
||||
def get_list(self) -> Response:
|
||||
"""Get a list of dashboards
|
||||
---
|
||||
get:
|
||||
description: >-
|
||||
Get a list of dashboards
|
||||
parameters:
|
||||
- in: query
|
||||
schema:
|
||||
type: integer
|
||||
name: page_size
|
||||
description: Number of results per page
|
||||
responses:
|
||||
200:
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DashboardListResponse'
|
||||
"""
|
||||
# Implementation
|
||||
```
|
||||
365
docs/developer_portal/guidelines/backend/dao-style-guidelines.md
Normal file
365
docs/developer_portal/guidelines/backend/dao-style-guidelines.md
Normal file
@@ -0,0 +1,365 @@
|
||||
---
|
||||
title: DAO Style Guidelines and Best Practices
|
||||
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.
|
||||
-->
|
||||
|
||||
# DAO Style Guidelines and Best Practices
|
||||
|
||||
A Data Access Object (DAO) is a pattern that provides an abstract interface to the SQLAlchemy Object Relational Mapper (ORM). The DAOs are critical as they form the building block of the application which are wrapped by the associated commands and RESTful API endpoints.
|
||||
|
||||
Currently there are numerous inconsistencies and violation of the DRY principal within the codebase as it relates to DAOs and ORMs—unnecessary commits, non-ACID transactions, etc.—which makes the code unnecessarily complex and convoluted. Addressing the underlying issues with the DAOs _should_ help simplify the downstream operations and improve the developer experience.
|
||||
|
||||
To ensure consistency the following rules should be adhered to:
|
||||
|
||||
1. All database operations (including testing) should be defined within a DAO, i.e., there should not be any explicit `db.session.add`, `db.session.merge`, etc. calls outside of a DAO.
|
||||
|
||||
2. A DAO should use `create`, `update`, `delete`, `upsert` terms—typical database operations which ensure consistency with commands—rather than action based terms like `save`, `saveas`, `override`, etc.
|
||||
|
||||
3. Sessions should be managed via a [context manager](https://docs.sqlalchemy.org/en/20/orm/session_transaction.html#begin-once) which auto-commits on success and rolls back on failure, i.e., there should be no explicit `db.session.commit` or `db.session.rollback` calls within the DAO.
|
||||
|
||||
4. There should be a single atomic transaction representing the entirety of the operation, i.e., when creating a dataset with associated columns and metrics either all the changes succeed when the transaction is committed, or all the changes are undone when the transaction is rolled back. SQLAlchemy supports [nested transactions](https://docs.sqlalchemy.org/en/20/orm/session_transaction.html#nested-transaction) via the `begin_nested` method which can be nested—inline with how DAOs are invoked.
|
||||
|
||||
5. The database layer should adopt a "shift left" mentality i.e., uniqueness/foreign key constraints, relationships, cascades, etc. should all be defined in the database layer rather than being enforced in the application layer.
|
||||
|
||||
## DAO Implementation Examples
|
||||
|
||||
### Basic DAO Structure
|
||||
|
||||
```python
|
||||
from typing import List, Optional, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from superset.extensions import db
|
||||
from superset.models.dashboard import Dashboard
|
||||
|
||||
class DashboardDAO:
|
||||
"""Data Access Object for Dashboard operations"""
|
||||
|
||||
@classmethod
|
||||
def find_by_id(cls, dashboard_id: int) -> Optional[Dashboard]:
|
||||
"""Find dashboard by ID"""
|
||||
return db.session.query(Dashboard).filter_by(id=dashboard_id).first()
|
||||
|
||||
@classmethod
|
||||
def find_by_ids(cls, dashboard_ids: List[int]) -> List[Dashboard]:
|
||||
"""Find dashboards by list of IDs"""
|
||||
return db.session.query(Dashboard).filter(
|
||||
Dashboard.id.in_(dashboard_ids)
|
||||
).all()
|
||||
|
||||
@classmethod
|
||||
def create(cls, properties: Dict[str, Any]) -> Dashboard:
|
||||
"""Create a new dashboard"""
|
||||
with db.session.begin():
|
||||
dashboard = Dashboard(**properties)
|
||||
db.session.add(dashboard)
|
||||
db.session.flush() # Get the ID
|
||||
return dashboard
|
||||
|
||||
@classmethod
|
||||
def update(cls, dashboard: Dashboard, properties: Dict[str, Any]) -> Dashboard:
|
||||
"""Update an existing dashboard"""
|
||||
with db.session.begin():
|
||||
for key, value in properties.items():
|
||||
setattr(dashboard, key, value)
|
||||
return dashboard
|
||||
|
||||
@classmethod
|
||||
def delete(cls, dashboard: Dashboard) -> None:
|
||||
"""Delete a dashboard"""
|
||||
with db.session.begin():
|
||||
db.session.delete(dashboard)
|
||||
```
|
||||
|
||||
### Complex DAO Operations
|
||||
|
||||
```python
|
||||
class DatasetDAO:
|
||||
"""Data Access Object for Dataset operations"""
|
||||
|
||||
@classmethod
|
||||
def create_with_columns_and_metrics(
|
||||
cls,
|
||||
dataset_properties: Dict[str, Any],
|
||||
columns: List[Dict[str, Any]],
|
||||
metrics: List[Dict[str, Any]]
|
||||
) -> Dataset:
|
||||
"""Create dataset with associated columns and metrics atomically"""
|
||||
with db.session.begin():
|
||||
# Create the dataset
|
||||
dataset = Dataset(**dataset_properties)
|
||||
db.session.add(dataset)
|
||||
db.session.flush() # Get the dataset ID
|
||||
|
||||
# Create columns
|
||||
for column_props in columns:
|
||||
column_props['dataset_id'] = dataset.id
|
||||
column = TableColumn(**column_props)
|
||||
db.session.add(column)
|
||||
|
||||
# Create metrics
|
||||
for metric_props in metrics:
|
||||
metric_props['dataset_id'] = dataset.id
|
||||
metric = SqlMetric(**metric_props)
|
||||
db.session.add(metric)
|
||||
|
||||
return dataset
|
||||
|
||||
@classmethod
|
||||
def bulk_delete(cls, dataset_ids: List[int]) -> int:
|
||||
"""Delete multiple datasets and return count"""
|
||||
with db.session.begin():
|
||||
count = db.session.query(Dataset).filter(
|
||||
Dataset.id.in_(dataset_ids)
|
||||
).delete(synchronize_session=False)
|
||||
return count
|
||||
```
|
||||
|
||||
### Query Methods
|
||||
|
||||
```python
|
||||
class DashboardDAO:
|
||||
@classmethod
|
||||
def find_by_slug(cls, slug: str) -> Optional[Dashboard]:
|
||||
"""Find dashboard by slug"""
|
||||
return db.session.query(Dashboard).filter_by(slug=slug).first()
|
||||
|
||||
@classmethod
|
||||
def find_by_owner(cls, owner_id: int) -> List[Dashboard]:
|
||||
"""Find all dashboards owned by a user"""
|
||||
return db.session.query(Dashboard).filter_by(
|
||||
created_by_fk=owner_id
|
||||
).all()
|
||||
|
||||
@classmethod
|
||||
def search(
|
||||
cls,
|
||||
query: str,
|
||||
page: int = 0,
|
||||
page_size: int = 25
|
||||
) -> Tuple[List[Dashboard], int]:
|
||||
"""Search dashboards with pagination"""
|
||||
base_query = db.session.query(Dashboard).filter(
|
||||
Dashboard.dashboard_title.ilike(f"%{query}%")
|
||||
)
|
||||
|
||||
total_count = base_query.count()
|
||||
dashboards = base_query.offset(page * page_size).limit(page_size).all()
|
||||
|
||||
return dashboards, total_count
|
||||
```
|
||||
|
||||
### Error Handling in DAOs
|
||||
|
||||
```python
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from superset.exceptions import DAOCreateFailedError, DAODeleteFailedError
|
||||
|
||||
class DashboardDAO:
|
||||
@classmethod
|
||||
def create(cls, properties: Dict[str, Any]) -> Dashboard:
|
||||
"""Create a new dashboard with error handling"""
|
||||
try:
|
||||
with db.session.begin():
|
||||
dashboard = Dashboard(**properties)
|
||||
db.session.add(dashboard)
|
||||
db.session.flush()
|
||||
return dashboard
|
||||
except IntegrityError as ex:
|
||||
raise DAOCreateFailedError(str(ex)) from ex
|
||||
|
||||
@classmethod
|
||||
def delete(cls, dashboard: Dashboard) -> None:
|
||||
"""Delete a dashboard with error handling"""
|
||||
try:
|
||||
with db.session.begin():
|
||||
db.session.delete(dashboard)
|
||||
except IntegrityError as ex:
|
||||
raise DAODeleteFailedError(
|
||||
f"Cannot delete dashboard {dashboard.id}: {str(ex)}"
|
||||
) from ex
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use Class Methods
|
||||
|
||||
All DAO methods should be class methods (`@classmethod`) rather than instance methods:
|
||||
|
||||
```python
|
||||
# ✅ Good
|
||||
class DashboardDAO:
|
||||
@classmethod
|
||||
def find_by_id(cls, dashboard_id: int) -> Optional[Dashboard]:
|
||||
return db.session.query(Dashboard).filter_by(id=dashboard_id).first()
|
||||
|
||||
# ❌ Avoid
|
||||
class DashboardDAO:
|
||||
def find_by_id(self, dashboard_id: int) -> Optional[Dashboard]:
|
||||
return db.session.query(Dashboard).filter_by(id=dashboard_id).first()
|
||||
```
|
||||
|
||||
### 2. Use Context Managers for Transactions
|
||||
|
||||
Always use context managers to ensure proper transaction handling:
|
||||
|
||||
```python
|
||||
# ✅ Good - automatic commit/rollback
|
||||
@classmethod
|
||||
def create(cls, properties: Dict[str, Any]) -> Dashboard:
|
||||
with db.session.begin():
|
||||
dashboard = Dashboard(**properties)
|
||||
db.session.add(dashboard)
|
||||
return dashboard
|
||||
|
||||
# ❌ Avoid - manual commit/rollback
|
||||
@classmethod
|
||||
def create(cls, properties: Dict[str, Any]) -> Dashboard:
|
||||
try:
|
||||
dashboard = Dashboard(**properties)
|
||||
db.session.add(dashboard)
|
||||
db.session.commit()
|
||||
return dashboard
|
||||
except Exception:
|
||||
db.session.rollback()
|
||||
raise
|
||||
```
|
||||
|
||||
### 3. Use Descriptive Method Names
|
||||
|
||||
Method names should clearly indicate the operation:
|
||||
|
||||
```python
|
||||
# ✅ Good - clear CRUD operations
|
||||
create()
|
||||
update()
|
||||
delete()
|
||||
find_by_id()
|
||||
find_by_slug()
|
||||
|
||||
# ❌ Avoid - ambiguous names
|
||||
save()
|
||||
remove()
|
||||
get()
|
||||
```
|
||||
|
||||
### 4. Type Hints
|
||||
|
||||
Always include type hints for parameters and return values:
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def find_by_ids(cls, dashboard_ids: List[int]) -> List[Dashboard]:
|
||||
"""Find dashboards by list of IDs"""
|
||||
return db.session.query(Dashboard).filter(
|
||||
Dashboard.id.in_(dashboard_ids)
|
||||
).all()
|
||||
```
|
||||
|
||||
### 5. Batch Operations
|
||||
|
||||
Provide efficient batch operations when needed:
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def bulk_update_published_status(
|
||||
cls,
|
||||
dashboard_ids: List[int],
|
||||
published: bool
|
||||
) -> int:
|
||||
"""Update published status for multiple dashboards"""
|
||||
with db.session.begin():
|
||||
count = db.session.query(Dashboard).filter(
|
||||
Dashboard.id.in_(dashboard_ids)
|
||||
).update(
|
||||
{Dashboard.published: published},
|
||||
synchronize_session=False
|
||||
)
|
||||
return count
|
||||
```
|
||||
|
||||
## Testing DAOs
|
||||
|
||||
### Unit Tests for DAOs
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from superset.dashboards.dao import DashboardDAO
|
||||
from superset.models.dashboard import Dashboard
|
||||
|
||||
def test_dashboard_create(session):
|
||||
"""Test creating a dashboard"""
|
||||
properties = {
|
||||
"dashboard_title": "Test Dashboard",
|
||||
"slug": "test-dashboard"
|
||||
}
|
||||
|
||||
dashboard = DashboardDAO.create(properties)
|
||||
|
||||
assert dashboard.id is not None
|
||||
assert dashboard.dashboard_title == "Test Dashboard"
|
||||
assert dashboard.slug == "test-dashboard"
|
||||
|
||||
def test_dashboard_find_by_slug(session):
|
||||
"""Test finding dashboard by slug"""
|
||||
# Create test data
|
||||
dashboard = Dashboard(
|
||||
dashboard_title="Test Dashboard",
|
||||
slug="test-dashboard"
|
||||
)
|
||||
session.add(dashboard)
|
||||
session.commit()
|
||||
|
||||
# Test the DAO method
|
||||
found_dashboard = DashboardDAO.find_by_slug("test-dashboard")
|
||||
|
||||
assert found_dashboard is not None
|
||||
assert found_dashboard.dashboard_title == "Test Dashboard"
|
||||
|
||||
def test_dashboard_delete(session):
|
||||
"""Test deleting a dashboard"""
|
||||
dashboard = Dashboard(dashboard_title="Test Dashboard")
|
||||
session.add(dashboard)
|
||||
session.commit()
|
||||
dashboard_id = dashboard.id
|
||||
|
||||
DashboardDAO.delete(dashboard)
|
||||
|
||||
deleted_dashboard = DashboardDAO.find_by_id(dashboard_id)
|
||||
assert deleted_dashboard is None
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
```python
|
||||
def test_create_dataset_with_columns_atomic(app_context):
|
||||
"""Test that creating dataset with columns is atomic"""
|
||||
dataset_properties = {"table_name": "test_table"}
|
||||
columns = [{"column_name": "col1"}, {"column_name": "col2"}]
|
||||
|
||||
# This should succeed completely or fail completely
|
||||
dataset = DatasetDAO.create_with_columns_and_metrics(
|
||||
dataset_properties, columns, []
|
||||
)
|
||||
|
||||
assert dataset.id is not None
|
||||
assert len(dataset.columns) == 2
|
||||
```
|
||||
148
docs/developer_portal/guidelines/design-guidelines.md
Normal file
148
docs/developer_portal/guidelines/design-guidelines.md
Normal file
@@ -0,0 +1,148 @@
|
||||
---
|
||||
title: Design Guidelines
|
||||
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.
|
||||
-->
|
||||
|
||||
# Design Guidelines
|
||||
|
||||
This is an area to host resources and documentation supporting the evolution and proper use of Superset design system elements. If content is to be added to this section or requires revisiting, a proposal should be submitted to the `dev@superset.apache.org` email list with either a text proposal or a link to a GitHub issue providing the markdown that will be added to this wiki. The Dev list will have the chance to review the proposal and arrive at lazy consensus. A committer may then copy/paste the markdown to this wiki, and make it public.
|
||||
|
||||
## Capitalization Guidelines
|
||||
|
||||
### Sentence case
|
||||
|
||||
Use sentence-case capitalization for everything in the UI (except these **).
|
||||
|
||||
Sentence case is predominantly lowercase. Capitalize only the initial character of the first word, and other words that require capitalization, like:
|
||||
|
||||
* **Proper nouns.** Objects in the product _are not_ considered proper nouns e.g. dashboards, charts, saved queries etc. Proprietary feature names eg. SQL Lab, Preset Manager _are_ considered proper nouns
|
||||
* **Acronyms** (e.g. CSS, HTML)
|
||||
* When referring to **UI labels that are themselves capitalized** from sentence case (e.g. page titles - Dashboards page, Charts page, Saved queries page, etc.)
|
||||
* User input that is reflected in the UI. E.g. a user-named a dashboard tab
|
||||
|
||||
**Sentence case vs. Title case:** Title case: "A Dog Takes a Walk in Paris" Sentence case: "A dog takes a walk in Paris"
|
||||
|
||||
**Why sentence case?**
|
||||
|
||||
* It's generally accepted as the quickest to read
|
||||
* It's the easiest form to distinguish between common and proper nouns
|
||||
|
||||
### How to refer to UI elements
|
||||
|
||||
When writing about a UI element, use the same capitalization as used in the UI.
|
||||
|
||||
For example, if an input field is labeled "Name" then you refer to this as the "Name input field". Similarly, if a button has the label "Save" in it, then it is correct to refer to the "Save button".
|
||||
|
||||
Where a product page is titled "Settings", you refer to this in writing as follows: "Edit your personal information on the Settings page".
|
||||
|
||||
Often a product page will have the same title as the objects it contains. In this case, refer to the page as it appears in the UI, and the objects as common nouns:
|
||||
|
||||
* Upload a dashboard on the Dashboards page
|
||||
* Go to Dashboards
|
||||
* View dashboard
|
||||
* View all dashboards
|
||||
* Upload CSS templates on the CSS templates page
|
||||
* Queries that you save will appear on the Saved queries page
|
||||
* Create custom queries in SQL Lab then create dashboards
|
||||
|
||||
### **Exceptions to sentence case:**
|
||||
|
||||
1. Acronyms and abbreviations. Examples: URL, CSV, XML
|
||||
|
||||
## Button Design Guidelines
|
||||
|
||||
### Overview
|
||||
|
||||
**Button variants:**
|
||||
|
||||
1. Primary
|
||||
2. Secondary
|
||||
3. Tertiary
|
||||
4. Destructive
|
||||
|
||||
**Button styles:** Each button variant has three styles:
|
||||
|
||||
1. Text
|
||||
2. Icon+text
|
||||
3. Icon only
|
||||
|
||||
Primary buttons have a fourth style: dropdown.
|
||||
|
||||
**Usage:** Buttons communicate actions that users can take. Do not use for navigations, instead use links.
|
||||
|
||||
**Purpose:**
|
||||
|
||||
| Button Type | Description |
|
||||
|------------|-------------|
|
||||
| Primary | Main call to action, just 1 per page not including modals or main headers |
|
||||
| Secondary | Secondary actions, always in conjunction with a primary |
|
||||
| Tertiary | For less prominent actions; can be used in isolation or paired with a primary button |
|
||||
| Destructive | For actions that could have destructive effects on the user's data |
|
||||
|
||||
## Error Message Design Guidelines
|
||||
|
||||
### Definition
|
||||
|
||||
Interface errors appear when the application can't do what the user wants, typically because:
|
||||
|
||||
- The app technically fails to complete the request
|
||||
- The app can't understand the user input
|
||||
- The user tries to combine operations that can't work together
|
||||
|
||||
In all cases, encountering errors increases user friction and frustration while trying to use the application. Providing an error experience that helps the user understand what happened and their next steps is key to building user confidence and increasing engagement.
|
||||
|
||||
### General best practices
|
||||
|
||||
**The best error experience is no error at all.** Before implementing error patterns, consider what you might do in the interface before the user would encounter an error to prevent it from happening at all. This might look like:
|
||||
|
||||
- Providing tooltips or microcopy to help users understand how to interact with the interface
|
||||
- Disabling parts of the UI until desired conditions are met (e.g. disabling a Save button until mandatory fields in a form are completed)
|
||||
- Correcting an error automatically (e.g. autocorrecting spelling errors)
|
||||
|
||||
**Only report errors users care about.** The only errors that should appear in the interface are errors that require user acknowledgement and action (even if that action is "try again later" or "contact support").
|
||||
|
||||
**Do not start the user in an error state.** If user inputs are required to display an initial interface (e.g. a chart in Explore), use empty states or field highlighting to drive users to the required action.
|
||||
|
||||
### Patterns
|
||||
|
||||
#### Pattern selection
|
||||
|
||||
Select one pattern per error (e.g. do not implement an inline and banner pattern for the same error).
|
||||
|
||||
When the error... | Use...
|
||||
---------------- | ------
|
||||
Is directly related to a UI control | Inline error
|
||||
Is not directly related to a UI control | Banner error
|
||||
|
||||
#### Inline
|
||||
|
||||
Inline errors are used when the source of the error is directly related to a UI control (text input, selector, etc.) such as the user not populating a required field or entering a number in a text field.
|
||||
|
||||
##### Anatomy
|
||||
|
||||
Use the `LabeledErrorBoundInput` component for this error pattern.
|
||||
|
||||
1. **Message** Provides direction on how to populate the input correctly.
|
||||
|
||||
##### Implementation details
|
||||
|
||||
- Where and when relevant, scroll the screen to the UI control with the error
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Frontend Style Guidelines
|
||||
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.
|
||||
-->
|
||||
|
||||
# Frontend Style Guidelines
|
||||
|
||||
This is a list of statements that describe how we do frontend development in Superset. While they might not be 100% true for all files in the repo, they represent the gold standard we strive towards for frontend quality and style.
|
||||
|
||||
* We develop using TypeScript.
|
||||
* We use React for building components, and Redux to manage app/global state.
|
||||
* See: [Component Style Guidelines and Best Practices](./frontend/component-style-guidelines)
|
||||
* We prefer functional components to class components and use hooks for local component state.
|
||||
* We use [Ant Design](https://ant.design/) components from our component library whenever possible, only building our own custom components when it's required.
|
||||
* We use [@emotion](https://emotion.sh/docs/introduction) to provide styling for our components, co-locating styling within component files.
|
||||
* See: [Emotion Styling Guidelines and Best Practices](./frontend/emotion-styling-guidelines)
|
||||
* We use Jest for unit tests, React Testing Library for component tests, and Cypress for end-to-end tests.
|
||||
* See: [Testing Guidelines and Best Practices](./frontend/testing-guidelines)
|
||||
* We add tests for every new component or file added to the frontend.
|
||||
* We organize our repo so similar files live near each other, and tests are co-located with the files they test.
|
||||
* We prefer small, easily testable files and components.
|
||||
* We use ESLint and Prettier to automatically fix lint errors and format the code.
|
||||
* We do not debate code formatting style in PRs, instead relying on automated tooling to enforce it.
|
||||
* If there's not a linting rule, we don't have a rule!
|
||||
* We use [React Storybook](https://storybook.js.org/) and [Applitools](https://applitools.com/) to help preview/test and stabilize our components
|
||||
@@ -0,0 +1,258 @@
|
||||
---
|
||||
title: Component Style Guidelines and Best Practices
|
||||
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.
|
||||
-->
|
||||
|
||||
# Component Style Guidelines and Best Practices
|
||||
|
||||
This documentation illustrates how we approach component development in Superset and provides examples to help you in writing new components or updating existing ones by following our community-approved standards.
|
||||
|
||||
This guide is intended primarily for reusable components. Whenever possible, all new components should be designed with reusability in mind.
|
||||
|
||||
## General Guidelines
|
||||
|
||||
- We use [Ant Design](https://ant.design/) as our component library. Do not build a new component if Ant Design provides one but rather instead extend or customize what the library provides
|
||||
- Always style your component using Emotion and always prefer the theme variables whenever applicable. See: [Emotion Styling Guidelines and Best Practices](./emotion-styling-guidelines)
|
||||
- All components should be made to be reusable whenever possible
|
||||
- All components should follow the structure and best practices as detailed below
|
||||
|
||||
## Directory and component structure
|
||||
|
||||
```
|
||||
superset-frontend/src/components
|
||||
{ComponentName}/
|
||||
index.tsx
|
||||
{ComponentName}.test.tsx
|
||||
{ComponentName}.stories.tsx
|
||||
```
|
||||
|
||||
**Components root directory:** Components that are meant to be re-used across different parts of the application should go in the `superset-frontend/src/components` directory. Components that are meant to be specific for a single part of the application should be located in the nearest directory where the component is used, for example, `superset-frontend/src/Explore/components`
|
||||
|
||||
**Exporting the component:** All components within the `superset-frontend/src/components` directory should be exported from `superset-frontend/src/components/index.ts` to facilitate their imports by other components
|
||||
|
||||
**Component directory name:** Use `PascalCase` for the component directory name
|
||||
|
||||
**Storybook:** Components should come with a storybook file whenever applicable, with the following naming convention `{ComponentName}.stories.tsx`. More details about Storybook below
|
||||
|
||||
**Unit and end-to-end tests:** All components should come with unit tests using Jest and React Testing Library. The file name should follow this naming convention `{ComponentName}.test.tsx.` Read the [Testing Guidelines and Best Practices](./testing-guidelines) for more details about tests
|
||||
|
||||
## Component Development Best Practices
|
||||
|
||||
### Use TypeScript
|
||||
|
||||
All new components should be written in TypeScript. This helps catch errors early and provides better development experience with IDE support.
|
||||
|
||||
```tsx
|
||||
interface ComponentProps {
|
||||
title: string;
|
||||
isVisible?: boolean;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export const MyComponent: React.FC<ComponentProps> = ({
|
||||
title,
|
||||
isVisible = true,
|
||||
onClose
|
||||
}) => {
|
||||
// Component implementation
|
||||
};
|
||||
```
|
||||
|
||||
### Prefer Functional Components
|
||||
|
||||
Use functional components with hooks instead of class components:
|
||||
|
||||
```tsx
|
||||
// ✅ Good - Functional component with hooks
|
||||
export const MyComponent: React.FC<Props> = ({ data }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Effect logic
|
||||
}, []);
|
||||
|
||||
return <div>{/* Component JSX */}</div>;
|
||||
};
|
||||
|
||||
// ❌ Avoid - Class component
|
||||
class MyComponent extends React.Component {
|
||||
// Class implementation
|
||||
}
|
||||
```
|
||||
|
||||
### Follow Ant Design Patterns
|
||||
|
||||
Extend Ant Design components rather than building from scratch:
|
||||
|
||||
```tsx
|
||||
import { Button } from 'antd';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
// Custom styling using emotion
|
||||
`;
|
||||
```
|
||||
|
||||
### Reusability and Props Design
|
||||
|
||||
Design components with reusability in mind:
|
||||
|
||||
```tsx
|
||||
interface ButtonProps {
|
||||
variant?: 'primary' | 'secondary' | 'tertiary';
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const CustomButton: React.FC<ButtonProps> = ({
|
||||
variant = 'primary',
|
||||
size = 'medium',
|
||||
...props
|
||||
}) => {
|
||||
// Implementation
|
||||
};
|
||||
```
|
||||
|
||||
## Testing Components
|
||||
|
||||
Every component should include comprehensive tests:
|
||||
|
||||
```tsx
|
||||
// MyComponent.test.tsx
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { MyComponent } from './MyComponent';
|
||||
|
||||
test('renders component with title', () => {
|
||||
render(<MyComponent title="Test Title" />);
|
||||
expect(screen.getByText('Test Title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('calls onClose when close button is clicked', () => {
|
||||
const mockOnClose = jest.fn();
|
||||
render(<MyComponent title="Test" onClose={mockOnClose} />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /close/i }));
|
||||
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
```
|
||||
|
||||
## Storybook Stories
|
||||
|
||||
Create stories for visual testing and documentation:
|
||||
|
||||
```tsx
|
||||
// MyComponent.stories.tsx
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { MyComponent } from './MyComponent';
|
||||
|
||||
const meta: Meta<typeof MyComponent> = {
|
||||
title: 'Components/MyComponent',
|
||||
component: MyComponent,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
title: 'Default Component',
|
||||
isVisible: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Hidden: Story = {
|
||||
args: {
|
||||
title: 'Hidden Component',
|
||||
isVisible: false,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Use React.memo for Expensive Components
|
||||
|
||||
```tsx
|
||||
import React, { memo } from 'react';
|
||||
|
||||
export const ExpensiveComponent = memo<Props>(({ data }) => {
|
||||
// Expensive rendering logic
|
||||
return <div>{/* Component content */}</div>;
|
||||
});
|
||||
```
|
||||
|
||||
### Optimize Re-renders
|
||||
|
||||
Use `useCallback` and `useMemo` appropriately:
|
||||
|
||||
```tsx
|
||||
export const OptimizedComponent: React.FC<Props> = ({ items, onSelect }) => {
|
||||
const expensiveValue = useMemo(() => {
|
||||
return items.reduce((acc, item) => acc + item.value, 0);
|
||||
}, [items]);
|
||||
|
||||
const handleSelect = useCallback((id: string) => {
|
||||
onSelect(id);
|
||||
}, [onSelect]);
|
||||
|
||||
return <div>{/* Component content */}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
Ensure components are accessible:
|
||||
|
||||
```tsx
|
||||
export const AccessibleButton: React.FC<Props> = ({ children, onClick }) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Descriptive label"
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Error Boundaries
|
||||
|
||||
For components that might fail, consider error boundaries:
|
||||
|
||||
```tsx
|
||||
export const SafeComponent: React.FC<Props> = ({ children }) => {
|
||||
return (
|
||||
<ErrorBoundary fallback={<div>Something went wrong</div>}>
|
||||
{children}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,346 @@
|
||||
---
|
||||
title: Emotion Styling Guidelines and Best Practices
|
||||
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.
|
||||
-->
|
||||
|
||||
# Emotion Styling Guidelines and Best Practices
|
||||
|
||||
## Emotion Styling Guidelines
|
||||
|
||||
### DO these things:
|
||||
|
||||
- **DO** use `styled` when you want to include additional (nested) class selectors in your styles
|
||||
- **DO** use `styled` components when you intend to export a styled component for re-use elsewhere
|
||||
- **DO** use `css` when you want to amend/merge sets of styles compositionally
|
||||
- **DO** use `css` when you're making a small, or single-use set of styles for a component
|
||||
- **DO** move your style definitions from direct usage in the `css` prop to an external variable when they get long
|
||||
- **DO** prefer tagged template literals (`css={css\`...\`}`) over style objects wherever possible for maximum style portability/consistency
|
||||
- **DO** use `useTheme` to get theme variables
|
||||
|
||||
### DON'T do these things:
|
||||
|
||||
- **DON'T** use `styled` for small, single-use style tweaks that would be easier to read/review if they were inline
|
||||
- **DON'T** export incomplete Ant Design components
|
||||
|
||||
## When to use `css` or `styled`
|
||||
|
||||
### Use `css` for:
|
||||
|
||||
1. **Small, single-use styles**
|
||||
```tsx
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
const MyComponent = () => (
|
||||
<div
|
||||
css={css`
|
||||
margin: 8px;
|
||||
padding: 16px;
|
||||
`}
|
||||
>
|
||||
Content
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
2. **Composing styles**
|
||||
```tsx
|
||||
const baseStyles = css`
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
`;
|
||||
|
||||
const primaryStyles = css`
|
||||
${baseStyles}
|
||||
background-color: blue;
|
||||
color: white;
|
||||
`;
|
||||
|
||||
const secondaryStyles = css`
|
||||
${baseStyles}
|
||||
background-color: gray;
|
||||
color: black;
|
||||
`;
|
||||
```
|
||||
|
||||
3. **Conditional styling**
|
||||
```tsx
|
||||
const MyComponent = ({ isActive }: { isActive: boolean }) => (
|
||||
<div
|
||||
css={[
|
||||
baseStyles,
|
||||
isActive && activeStyles,
|
||||
]}
|
||||
>
|
||||
Content
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
### Use `styled` for:
|
||||
|
||||
1. **Reusable components**
|
||||
```tsx
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledButton = styled.button`
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background-color: ${({ theme }) => theme.colors.primary};
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.colors.primaryDark};
|
||||
}
|
||||
`;
|
||||
```
|
||||
|
||||
2. **Components with complex nested selectors**
|
||||
```tsx
|
||||
const StyledCard = styled.div`
|
||||
padding: 16px;
|
||||
border: 1px solid ${({ theme }) => theme.colors.border};
|
||||
|
||||
.card-header {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
|
||||
p {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
```
|
||||
|
||||
3. **Extending Ant Design components**
|
||||
```tsx
|
||||
import { Button } from 'antd';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const CustomButton = styled(Button)`
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
|
||||
&.ant-btn-primary {
|
||||
background: linear-gradient(45deg, #1890ff, #722ed1);
|
||||
}
|
||||
`;
|
||||
```
|
||||
|
||||
## Using Theme Variables
|
||||
|
||||
Always use theme variables for consistent styling:
|
||||
|
||||
```tsx
|
||||
import { useTheme } from '@emotion/react';
|
||||
|
||||
const MyComponent = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
background-color: ${theme.colors.grayscale.light5};
|
||||
color: ${theme.colors.grayscale.dark2};
|
||||
padding: ${theme.gridUnit * 4}px;
|
||||
border-radius: ${theme.borderRadius}px;
|
||||
`}
|
||||
>
|
||||
Content
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Responsive Design
|
||||
```tsx
|
||||
const ResponsiveContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
${({ theme }) => theme.breakpoints.up('md')} {
|
||||
flex-direction: row;
|
||||
}
|
||||
`;
|
||||
```
|
||||
|
||||
### Animation
|
||||
```tsx
|
||||
const FadeInComponent = styled.div`
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
|
||||
&.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
```
|
||||
|
||||
### Conditional Props
|
||||
```tsx
|
||||
interface StyledDivProps {
|
||||
isHighlighted?: boolean;
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div<StyledDivProps>`
|
||||
padding: 16px;
|
||||
background-color: ${({ isHighlighted, theme }) =>
|
||||
isHighlighted ? theme.colors.primary : theme.colors.grayscale.light5};
|
||||
|
||||
${({ size }) => {
|
||||
switch (size) {
|
||||
case 'small':
|
||||
return css`font-size: 12px;`;
|
||||
case 'large':
|
||||
return css`font-size: 18px;`;
|
||||
default:
|
||||
return css`font-size: 14px;`;
|
||||
}
|
||||
}}
|
||||
`;
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use Semantic CSS Properties
|
||||
```tsx
|
||||
// ✅ Good - semantic property names
|
||||
const Header = styled.h1`
|
||||
font-size: ${({ theme }) => theme.typography.sizes.xl};
|
||||
margin-bottom: ${({ theme }) => theme.gridUnit * 4}px;
|
||||
`;
|
||||
|
||||
// ❌ Avoid - magic numbers
|
||||
const Header = styled.h1`
|
||||
font-size: 24px;
|
||||
margin-bottom: 16px;
|
||||
`;
|
||||
```
|
||||
|
||||
### 2. Group Related Styles
|
||||
```tsx
|
||||
// ✅ Good - grouped styles
|
||||
const Card = styled.div`
|
||||
/* Layout */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: ${({ theme }) => theme.gridUnit * 4}px;
|
||||
|
||||
/* Appearance */
|
||||
background-color: ${({ theme }) => theme.colors.grayscale.light5};
|
||||
border: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
|
||||
border-radius: ${({ theme }) => theme.borderRadius}px;
|
||||
|
||||
/* Typography */
|
||||
font-family: ${({ theme }) => theme.typography.families.sansSerif};
|
||||
color: ${({ theme }) => theme.colors.grayscale.dark2};
|
||||
`;
|
||||
```
|
||||
|
||||
### 3. Extract Complex Styles
|
||||
```tsx
|
||||
// ✅ Good - extract complex styles to variables
|
||||
const complexGradient = css`
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
${({ theme }) => theme.colors.primary} 0%,
|
||||
${({ theme }) => theme.colors.secondary} 100%
|
||||
);
|
||||
`;
|
||||
|
||||
const GradientButton = styled.button`
|
||||
${complexGradient}
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
color: white;
|
||||
`;
|
||||
```
|
||||
|
||||
### 4. Avoid Deep Nesting
|
||||
```tsx
|
||||
// ✅ Good - shallow nesting
|
||||
const Navigation = styled.nav`
|
||||
.nav-item {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
// ❌ Avoid - deep nesting
|
||||
const Navigation = styled.nav`
|
||||
ul {
|
||||
li {
|
||||
a {
|
||||
span {
|
||||
color: blue; /* Too nested */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
### 1. Avoid Inline Functions in CSS
|
||||
```tsx
|
||||
// ✅ Good - external function
|
||||
const getBackgroundColor = (isActive: boolean, theme: any) =>
|
||||
isActive ? theme.colors.primary : theme.colors.secondary;
|
||||
|
||||
const Button = styled.button<{ isActive: boolean }>`
|
||||
background-color: ${({ isActive, theme }) => getBackgroundColor(isActive, theme)};
|
||||
`;
|
||||
|
||||
// ❌ Avoid - inline function (creates new function on each render)
|
||||
const Button = styled.button<{ isActive: boolean }>`
|
||||
background-color: ${({ isActive, theme }) =>
|
||||
isActive ? theme.colors.primary : theme.colors.secondary};
|
||||
`;
|
||||
```
|
||||
|
||||
### 2. Use CSS Objects for Dynamic Styles
|
||||
```tsx
|
||||
// For highly dynamic styles, consider CSS objects
|
||||
const dynamicStyles = (props: Props) => ({
|
||||
backgroundColor: props.color,
|
||||
fontSize: `${props.size}px`,
|
||||
// ... other dynamic properties
|
||||
});
|
||||
|
||||
const DynamicComponent = (props: Props) => (
|
||||
<div css={dynamicStyles(props)}>
|
||||
Content
|
||||
</div>
|
||||
);
|
||||
```
|
||||
297
docs/developer_portal/guidelines/frontend/testing-guidelines.md
Normal file
297
docs/developer_portal/guidelines/frontend/testing-guidelines.md
Normal file
@@ -0,0 +1,297 @@
|
||||
---
|
||||
title: Testing Guidelines and Best Practices
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Testing Guidelines and Best Practices
|
||||
|
||||
We feel that tests are an important part of a feature and not an additional or optional effort. That's why we colocate test files with functionality and sometimes write tests upfront to help validate requirements and shape the API of our components. Every new component or file added should have an associated test file with the `.test` extension.
|
||||
|
||||
We use Jest, React Testing Library (RTL), and Cypress to write our unit, integration, and end-to-end tests. For each type, we have a set of best practices/tips described below:
|
||||
|
||||
## Jest
|
||||
|
||||
### Write simple, standalone tests
|
||||
|
||||
The importance of simplicity is often overlooked in test cases. Clear, dumb code should always be preferred over complex ones. The test cases should be pretty much standalone and should not involve any external logic if not absolutely necessary. That's because you want the corpus of the tests to be easy to read and understandable at first sight.
|
||||
|
||||
### Avoid nesting when you're testing
|
||||
|
||||
Avoid the use of `describe` blocks in favor of inlined tests. If your tests start to grow and you feel the need to group tests, prefer to break them into multiple test files. Check this awesome [article](https://kentcdodds.com/blog/avoid-nesting-when-youre-testing) written by [Kent C. Dodds](https://kentcdodds.com/) about this topic.
|
||||
|
||||
### No warnings!
|
||||
|
||||
Your tests shouldn't trigger warnings. This is really common when testing async functionality. It's really difficult to read test results when we have a bunch of warnings.
|
||||
|
||||
## React Testing Library (RTL)
|
||||
|
||||
### Keep accessibility in mind when writing your tests
|
||||
|
||||
One of the most important points of RTL is accessibility and this is also a very important point for us. We should try our best to follow the RTL [Priority](https://testing-library.com/docs/queries/about/#priority) when querying for elements in our tests. `getByTestId` is not viewable by the user and should only be used when the element isn't accessible in any other way.
|
||||
|
||||
### Don't use `act` unnecessarily
|
||||
|
||||
`render` and `fireEvent` are already wrapped in `act`, so wrapping them in `act` again is a common mistake. Some solutions to the warnings related to async testing can be found in the RTL docs.
|
||||
|
||||
## Example Test Structure
|
||||
|
||||
```tsx
|
||||
// MyComponent.test.tsx
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { MyComponent } from './MyComponent';
|
||||
|
||||
// ✅ Good - Simple, standalone test
|
||||
test('renders loading state initially', () => {
|
||||
render(<MyComponent />);
|
||||
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// ✅ Good - Tests user interaction
|
||||
test('calls onSubmit when form is submitted', async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockOnSubmit = jest.fn();
|
||||
|
||||
render(<MyComponent onSubmit={mockOnSubmit} />);
|
||||
|
||||
await user.type(screen.getByLabelText('Username'), 'testuser');
|
||||
await user.click(screen.getByRole('button', { name: 'Submit' }));
|
||||
|
||||
expect(mockOnSubmit).toHaveBeenCalledWith({ username: 'testuser' });
|
||||
});
|
||||
|
||||
// ✅ Good - Tests async behavior
|
||||
test('displays error message when API call fails', async () => {
|
||||
const mockFetch = jest.fn().mockRejectedValue(new Error('API Error'));
|
||||
global.fetch = mockFetch;
|
||||
|
||||
render(<MyComponent />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Error: API Error')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Testing Best Practices
|
||||
|
||||
### Use appropriate queries in priority order
|
||||
|
||||
1. **Accessible to everyone**: `getByRole`, `getByLabelText`, `getByPlaceholderText`, `getByText`
|
||||
2. **Semantic queries**: `getByAltText`, `getByTitle`
|
||||
3. **Test IDs**: `getByTestId` (last resort)
|
||||
|
||||
```tsx
|
||||
// ✅ Good - using accessible queries
|
||||
test('user can submit form', () => {
|
||||
render(<LoginForm />);
|
||||
|
||||
const usernameInput = screen.getByLabelText('Username');
|
||||
const passwordInput = screen.getByLabelText('Password');
|
||||
const submitButton = screen.getByRole('button', { name: 'Log in' });
|
||||
|
||||
// Test implementation
|
||||
});
|
||||
|
||||
// ❌ Avoid - using test IDs when better options exist
|
||||
test('user can submit form', () => {
|
||||
render(<LoginForm />);
|
||||
|
||||
const usernameInput = screen.getByTestId('username-input');
|
||||
const passwordInput = screen.getByTestId('password-input');
|
||||
const submitButton = screen.getByTestId('submit-button');
|
||||
|
||||
// Test implementation
|
||||
});
|
||||
```
|
||||
|
||||
### Test behavior, not implementation details
|
||||
|
||||
```tsx
|
||||
// ✅ Good - tests what the user experiences
|
||||
test('shows success message after successful form submission', async () => {
|
||||
render(<ContactForm />);
|
||||
|
||||
await userEvent.type(screen.getByLabelText('Email'), 'test@example.com');
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Submit' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Message sent successfully!')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
// ❌ Avoid - testing implementation details
|
||||
test('calls setState when form is submitted', () => {
|
||||
const component = shallow(<ContactForm />);
|
||||
const instance = component.instance();
|
||||
const spy = jest.spyOn(instance, 'setState');
|
||||
|
||||
instance.handleSubmit();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
```
|
||||
|
||||
### Mock external dependencies appropriately
|
||||
|
||||
```tsx
|
||||
// Mock API calls
|
||||
jest.mock('../api/userService', () => ({
|
||||
getUser: jest.fn(),
|
||||
createUser: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock components that aren't relevant to the test
|
||||
jest.mock('../Chart/Chart', () => {
|
||||
return function MockChart() {
|
||||
return <div data-testid="mock-chart">Chart Component</div>;
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
## Async Testing Patterns
|
||||
|
||||
### Testing async operations
|
||||
|
||||
```tsx
|
||||
test('loads and displays user data', async () => {
|
||||
const mockUser = { id: 1, name: 'John Doe' };
|
||||
const mockGetUser = jest.fn().mockResolvedValue(mockUser);
|
||||
|
||||
render(<UserProfile getUserData={mockGetUser} />);
|
||||
|
||||
// Wait for the async operation to complete
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(mockGetUser).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
```
|
||||
|
||||
### Testing loading states
|
||||
|
||||
```tsx
|
||||
test('shows loading spinner while fetching data', async () => {
|
||||
const mockGetUser = jest.fn().mockImplementation(
|
||||
() => new Promise(resolve => setTimeout(resolve, 100))
|
||||
);
|
||||
|
||||
render(<UserProfile getUserData={mockGetUser} />);
|
||||
|
||||
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Component-Specific Testing
|
||||
|
||||
### Testing form components
|
||||
|
||||
```tsx
|
||||
test('validates required fields', async () => {
|
||||
render(<RegistrationForm />);
|
||||
|
||||
const submitButton = screen.getByRole('button', { name: 'Register' });
|
||||
await userEvent.click(submitButton);
|
||||
|
||||
expect(screen.getByText('Username is required')).toBeInTheDocument();
|
||||
expect(screen.getByText('Email is required')).toBeInTheDocument();
|
||||
});
|
||||
```
|
||||
|
||||
### Testing modals and overlays
|
||||
|
||||
```tsx
|
||||
test('opens and closes modal', async () => {
|
||||
render(<ModalContainer />);
|
||||
|
||||
const openButton = screen.getByRole('button', { name: 'Open Modal' });
|
||||
await userEvent.click(openButton);
|
||||
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
|
||||
const closeButton = screen.getByRole('button', { name: 'Close' });
|
||||
await userEvent.click(closeButton);
|
||||
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
});
|
||||
```
|
||||
|
||||
### Testing with context providers
|
||||
|
||||
```tsx
|
||||
const renderWithTheme = (component: React.ReactElement) => {
|
||||
return render(
|
||||
<ThemeProvider theme={mockTheme}>
|
||||
{component}
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
test('applies theme colors correctly', () => {
|
||||
renderWithTheme(<ThemedButton />);
|
||||
|
||||
const button = screen.getByRole('button');
|
||||
expect(button).toHaveStyle({
|
||||
backgroundColor: mockTheme.colors.primary,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Performance Testing
|
||||
|
||||
### Testing with large datasets
|
||||
|
||||
```tsx
|
||||
test('handles large lists efficiently', () => {
|
||||
const largeDataset = Array.from({ length: 10000 }, (_, i) => ({
|
||||
id: i,
|
||||
name: `Item ${i}`,
|
||||
}));
|
||||
|
||||
const { container } = render(<VirtualizedList items={largeDataset} />);
|
||||
|
||||
// Should only render visible items
|
||||
const renderedItems = container.querySelectorAll('[data-testid="list-item"]');
|
||||
expect(renderedItems.length).toBeLessThan(100);
|
||||
});
|
||||
```
|
||||
|
||||
## Testing Accessibility
|
||||
|
||||
```tsx
|
||||
test('is accessible to screen readers', () => {
|
||||
render(<AccessibleForm />);
|
||||
|
||||
const form = screen.getByRole('form');
|
||||
const inputs = screen.getAllByRole('textbox');
|
||||
|
||||
inputs.forEach(input => {
|
||||
expect(input).toHaveAttribute('aria-label');
|
||||
});
|
||||
|
||||
expect(form).toHaveAttribute('aria-describedby');
|
||||
});
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user