mirror of
https://github.com/apache/superset.git
synced 2026-07-31 19:12:27 +00:00
Compare commits
1 Commits
chart-samp
...
msyavuz/fe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a39e3f10be |
497
superset-frontend/package-lock.json
generated
497
superset-frontend/package-lock.json
generated
@@ -66,6 +66,7 @@
|
||||
"@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars",
|
||||
"@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table",
|
||||
"@superset-ui/plugin-chart-point-cluster-map": "file:./plugins/plugin-chart-point-cluster-map",
|
||||
"@superset-ui/plugin-chart-sandpack": "file:./plugins/plugin-chart-sandpack",
|
||||
"@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table",
|
||||
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
|
||||
"@superset-ui/preset-chart-deckgl": "file:./plugins/preset-chart-deckgl",
|
||||
@@ -2713,6 +2714,206 @@
|
||||
"dev": true,
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.2.tgz",
|
||||
"integrity": "sha512-G5FPkgIiLjOgZMjqVjvuKQ1rGPtHogLldJr33eFJdVLtmwY+giGrlv/ewljLz6b9BSQLkjxuwBc6g6omDM+YxQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/commands": {
|
||||
"version": "6.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz",
|
||||
"integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.6.0",
|
||||
"@codemirror/view": "^6.27.0",
|
||||
"@lezer/common": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-css": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
||||
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.0.2",
|
||||
"@lezer/css": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-html": {
|
||||
"version": "6.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
|
||||
"integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/lang-css": "^6.0.0",
|
||||
"@codemirror/lang-javascript": "^6.0.0",
|
||||
"@codemirror/language": "^6.4.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/css": "^1.1.0",
|
||||
"@lezer/html": "^1.3.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-javascript": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz",
|
||||
"integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/javascript": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.12.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz",
|
||||
"integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
"@lezer/common": "^1.5.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0",
|
||||
"style-mod": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lint": {
|
||||
"version": "6.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.6.tgz",
|
||||
"integrity": "sha512-6Kp7r6XfCi/D/5sdXieMfg9pJU1bUEx96WITuLU6ESaKizCz0QHFMjY/TaFSbigDdEAIgi93itLBIUETP4oK+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.42.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz",
|
||||
"integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.42.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.42.1.tgz",
|
||||
"integrity": "sha512-ToN3oFc0nsxNUYVF5P0ztLgbC4UPPjPtA9aKYhkOKQaZASpOUo6ISXyQLP66ctVwlDc+j6Jv0uK5IFALkiXztg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.6.0",
|
||||
"crelt": "^1.0.6",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@codesandbox/nodebox": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.8.tgz",
|
||||
"integrity": "sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==",
|
||||
"license": "SEE LICENSE IN ./LICENSE",
|
||||
"dependencies": {
|
||||
"outvariant": "^1.4.0",
|
||||
"strict-event-emitter": "^0.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@codesandbox/sandpack-client": {
|
||||
"version": "2.19.8",
|
||||
"resolved": "https://registry.npmjs.org/@codesandbox/sandpack-client/-/sandpack-client-2.19.8.tgz",
|
||||
"integrity": "sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@codesandbox/nodebox": "0.1.8",
|
||||
"buffer": "^6.0.3",
|
||||
"dequal": "^2.0.2",
|
||||
"mime-db": "^1.52.0",
|
||||
"outvariant": "1.4.0",
|
||||
"static-browser-server": "1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@codesandbox/sandpack-client/node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@codesandbox/sandpack-react": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@codesandbox/sandpack-react/-/sandpack-react-2.20.0.tgz",
|
||||
"integrity": "sha512-takd1YpW/PMQ6KPQfvseWLHWklJovGY8QYj8MtWnskGKbjOGJ6uZfyZbcJ6aCFLQMpNyjTqz9AKNbvhCOZ1TUQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.4.0",
|
||||
"@codemirror/commands": "^6.1.3",
|
||||
"@codemirror/lang-css": "^6.0.1",
|
||||
"@codemirror/lang-html": "^6.4.0",
|
||||
"@codemirror/lang-javascript": "^6.1.2",
|
||||
"@codemirror/language": "^6.3.2",
|
||||
"@codemirror/state": "^6.2.0",
|
||||
"@codemirror/view": "^6.7.1",
|
||||
"@codesandbox/sandpack-client": "^2.19.8",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
"@react-hook/intersection-observer": "^3.1.1",
|
||||
"@stitches/core": "^1.2.6",
|
||||
"anser": "^2.1.1",
|
||||
"clean-set": "^1.1.2",
|
||||
"dequal": "^2.0.2",
|
||||
"escape-carriage": "^1.3.1",
|
||||
"lz-string": "^1.4.4",
|
||||
"react-devtools-inline": "4.4.0",
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17 || ^18 || ^19",
|
||||
"react-dom": "^16.8.0 || ^17 || ^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@codesandbox/sandpack-react/node_modules/react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
@@ -6850,6 +7051,63 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lezer/common": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz",
|
||||
"integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lezer/css": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.3.tgz",
|
||||
"integrity": "sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/highlight": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
|
||||
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/html": {
|
||||
"version": "1.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz",
|
||||
"integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/javascript": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz",
|
||||
"integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/lr": {
|
||||
"version": "1.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz",
|
||||
"integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@loaders.gl/3d-tiles": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@loaders.gl/3d-tiles/-/3d-tiles-4.3.4.tgz",
|
||||
@@ -7444,6 +7702,12 @@
|
||||
"pbf": "bin/pbf"
|
||||
}
|
||||
},
|
||||
"node_modules/@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@math.gl/core": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@math.gl/core/-/core-4.1.0.tgz",
|
||||
@@ -8642,6 +8906,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@open-draft/deferred-promise": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz",
|
||||
"integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@oxlint/binding-android-arm-eabi": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.62.0.tgz",
|
||||
@@ -9450,6 +9720,28 @@
|
||||
"integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-hook/intersection-observer": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-hook/intersection-observer/-/intersection-observer-3.1.2.tgz",
|
||||
"integrity": "sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-hook/passive-layout-effect": "^1.2.0",
|
||||
"intersection-observer": "^0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-hook/passive-layout-effect": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz",
|
||||
"integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-spring/animated": {
|
||||
"version": "9.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.5.tgz",
|
||||
@@ -10032,6 +10324,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@stitches/core": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz",
|
||||
"integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@storybook/addon-actions": {
|
||||
"version": "8.6.18",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.6.18.tgz",
|
||||
@@ -12197,6 +12495,10 @@
|
||||
"resolved": "plugins/plugin-chart-point-cluster-map",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/plugin-chart-sandpack": {
|
||||
"resolved": "plugins/plugin-chart-sandpack",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/plugin-chart-table": {
|
||||
"resolved": "plugins/plugin-chart-table",
|
||||
"link": true
|
||||
@@ -16393,7 +16695,6 @@
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmjs.org/anser/-/anser-2.3.5.tgz",
|
||||
"integrity": "sha512-vcZjxvvVoxTeR5XBNJB38oTu/7eDCZlwdz32N1eNgpyPF7j/Z7Idf+CUwQOkKKpJ7RJyjxgLHCM7vdIK0iCNMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ansi-colors": {
|
||||
@@ -18470,6 +18771,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/clean-set": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/clean-set/-/clean-set-1.1.2.tgz",
|
||||
"integrity": "sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/clean-stack": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
||||
@@ -20114,6 +20421,12 @@
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz",
|
||||
@@ -20650,6 +20963,19 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/d": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
|
||||
"integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"es5-ext": "^0.10.64",
|
||||
"type": "^2.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3": {
|
||||
"version": "3.5.17",
|
||||
"resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz",
|
||||
@@ -21717,7 +22043,6 @@
|
||||
"version": "16.4.7",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
||||
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -22294,6 +22619,22 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/es5-ext": {
|
||||
"version": "0.10.64",
|
||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
|
||||
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"es6-iterator": "^2.0.3",
|
||||
"es6-symbol": "^3.1.3",
|
||||
"esniff": "^2.0.1",
|
||||
"next-tick": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-error": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
||||
@@ -22301,6 +22642,30 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es6-iterator": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
|
||||
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"d": "1",
|
||||
"es5-ext": "^0.10.35",
|
||||
"es6-symbol": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-symbol": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz",
|
||||
"integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d": "^1.0.2",
|
||||
"ext": "^1.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
||||
@@ -22416,6 +22781,12 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-carriage": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz",
|
||||
"integrity": "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
@@ -23242,6 +23613,21 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esniff": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
|
||||
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d": "^1.0.1",
|
||||
"es5-ext": "^0.10.62",
|
||||
"event-emitter": "^0.3.5",
|
||||
"type": "^2.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
|
||||
@@ -23363,6 +23749,16 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/event-emitter": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
|
||||
"integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"d": "1",
|
||||
"es5-ext": "~0.10.14"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
@@ -23545,6 +23941,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ext": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
|
||||
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"type": "^2.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
@@ -27102,6 +27507,13 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/intersection-observer": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.10.0.tgz",
|
||||
"integrity": "sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==",
|
||||
"deprecated": "The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019.",
|
||||
"license": "W3C-20150513"
|
||||
},
|
||||
"node_modules/interweave": {
|
||||
"version": "13.1.1",
|
||||
"resolved": "https://registry.npmjs.org/interweave/-/interweave-13.1.1.tgz",
|
||||
@@ -36272,7 +36684,6 @@
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -36759,6 +37170,12 @@
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next-tick": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
|
||||
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ngeohash": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/ngeohash/-/ngeohash-0.6.3.tgz",
|
||||
@@ -38148,6 +38565,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/outvariant": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz",
|
||||
"integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/own-keys": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
|
||||
@@ -41145,6 +41568,15 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-devtools-inline": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/react-devtools-inline/-/react-devtools-inline-4.4.0.tgz",
|
||||
"integrity": "sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es6-symbol": "^3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-diff-viewer-continued": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-diff-viewer-continued/-/react-diff-viewer-continued-4.2.2.tgz",
|
||||
@@ -44424,6 +44856,18 @@
|
||||
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/static-browser-server": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/static-browser-server/-/static-browser-server-1.0.3.tgz",
|
||||
"integrity": "sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@open-draft/deferred-promise": "^2.1.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"mime-db": "^1.52.0",
|
||||
"outvariant": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/static-eval": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.1.tgz",
|
||||
@@ -44711,6 +45155,12 @@
|
||||
"bare-events": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strict-event-emitter": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz",
|
||||
"integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
@@ -45000,6 +45450,12 @@
|
||||
"webpack": "^5.27.0"
|
||||
}
|
||||
},
|
||||
"node_modules/style-mod": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
|
||||
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/style-to-object": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz",
|
||||
@@ -46615,6 +47071,12 @@
|
||||
"node": "^20.17.0 || >=22.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/type": {
|
||||
"version": "2.7.3",
|
||||
"resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz",
|
||||
"integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
@@ -47844,6 +48306,12 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/w3c-xmlserializer": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
||||
@@ -50796,6 +51264,27 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-sandpack": {
|
||||
"name": "@superset-ui/plugin-chart-sandpack",
|
||||
"version": "0.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@codesandbox/sandpack-react": "^2.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"jest": "^30.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"lodash": "^4.18.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-table": {
|
||||
"name": "@superset-ui/plugin-chart-table",
|
||||
"version": "0.20.3",
|
||||
@@ -50893,7 +51382,7 @@
|
||||
"@deck.gl/extensions": "~9.2.9",
|
||||
"@deck.gl/geo-layers": "~9.2.5",
|
||||
"@deck.gl/layers": "~9.2.5",
|
||||
"@deck.gl/mapbox": "^9.3.2",
|
||||
"@deck.gl/mapbox": "~9.3.2",
|
||||
"@deck.gl/mesh-layers": "~9.2.5",
|
||||
"@luma.gl/constants": "~9.2.5",
|
||||
"@luma.gl/core": "~9.2.5",
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",
|
||||
"@superset-ui/plugin-chart-point-cluster-map": "file:./plugins/plugin-chart-point-cluster-map",
|
||||
"@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars",
|
||||
"@superset-ui/plugin-chart-sandpack": "file:./plugins/plugin-chart-sandpack",
|
||||
"@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table",
|
||||
"@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table",
|
||||
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
|
||||
|
||||
@@ -50,6 +50,7 @@ export enum VizType {
|
||||
PivotTable = 'pivot_table_v2',
|
||||
Radar = 'radar',
|
||||
Rose = 'rose',
|
||||
Sandpack = 'sandpack',
|
||||
Sankey = 'sankey_v2',
|
||||
Scatter = 'echarts_timeseries_scatter',
|
||||
SmoothLine = 'echarts_timeseries_smooth',
|
||||
|
||||
46
superset-frontend/plugins/plugin-chart-sandpack/package.json
Normal file
46
superset-frontend/plugins/plugin-chart-sandpack/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "@superset-ui/plugin-chart-sandpack",
|
||||
"version": "0.1.0",
|
||||
"description": "Superset Chart - Render arbitrary Sandpack-powered apps against query data",
|
||||
"sideEffects": false,
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
"files": [
|
||||
"esm",
|
||||
"lib"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/apache/superset.git",
|
||||
"directory": "superset-frontend/plugins/plugin-chart-sandpack"
|
||||
},
|
||||
"keywords": [
|
||||
"superset",
|
||||
"sandpack"
|
||||
],
|
||||
"author": "Superset",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/apache/superset/issues"
|
||||
},
|
||||
"homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/plugin-chart-sandpack#readme",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codesandbox/sandpack-react": "^2.20.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"lodash": "^4.18.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"jest": "^30.3.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { styled } from '@apache-superset/core/theme';
|
||||
import { useMemo } from 'react';
|
||||
import { SandpackViewer } from './components/Sandpack/SandpackViewer';
|
||||
import { DEFAULT_APP_CODE, DEFAULT_DEPENDENCIES } from './consts';
|
||||
import { SandpackProps, SandpackStylesProps } from './types';
|
||||
|
||||
const Styles = styled.div<SandpackStylesProps>`
|
||||
height: ${({ height }) => height}px;
|
||||
width: ${({ width }) => width}px;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
function safeParseDependencies(raw?: string): Record<string, string> {
|
||||
if (!raw) return {};
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
||||
return parsed as Record<string, string>;
|
||||
}
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
export default function SandpackChart(props: SandpackProps) {
|
||||
const { data, height, width, formData } = props;
|
||||
const appCode = formData.appCode || DEFAULT_APP_CODE;
|
||||
const template = formData.template || 'react';
|
||||
const layout = formData.layout || 'split';
|
||||
const showNavigator = formData.showNavigator ?? false;
|
||||
|
||||
const dependencies = useMemo(
|
||||
() => safeParseDependencies(formData.dependencies || DEFAULT_DEPENDENCIES),
|
||||
[formData.dependencies],
|
||||
);
|
||||
|
||||
return (
|
||||
<Styles height={height} width={width}>
|
||||
<SandpackViewer
|
||||
appCode={appCode}
|
||||
data={data}
|
||||
dependencies={dependencies}
|
||||
template={template}
|
||||
layout={layout}
|
||||
showNavigator={showNavigator}
|
||||
height={height}
|
||||
/>
|
||||
</Styles>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export {
|
||||
CodeEditor,
|
||||
type CodeEditorProps,
|
||||
type CodeEditorMode,
|
||||
type CodeEditorTheme,
|
||||
} from '@superset-ui/core/components';
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface ControlHeaderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const ControlHeader = ({
|
||||
children,
|
||||
}: ControlHeaderProps): JSX.Element => (
|
||||
<div className="ControlHeader">
|
||||
<div className="pull-left">{children}</div>
|
||||
</div>
|
||||
);
|
||||
Binary file not shown.
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { debounce } from 'lodash';
|
||||
import { Constants } from '@superset-ui/core/components';
|
||||
|
||||
export const debounceFunc = debounce(
|
||||
(func: (val: string) => void, source: string) => func(source),
|
||||
Constants.SLOW_DEBOUNCE,
|
||||
);
|
||||
|
||||
export const DEFAULT_APP_CODE = `import data from './data.json';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div style={{ fontFamily: 'system-ui', padding: 16 }}>
|
||||
<h2>Rows: {data.length}</h2>
|
||||
<ul>
|
||||
{data.slice(0, 20).map((row, i) => (
|
||||
<li key={i}>{JSON.stringify(row)}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
`;
|
||||
|
||||
export const DEFAULT_DEPENDENCIES = `{
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
`;
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 390 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 390 KiB |
20
superset-frontend/plugins/plugin-chart-sandpack/src/index.ts
Normal file
20
superset-frontend/plugins/plugin-chart-sandpack/src/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export { default as SandpackChartPlugin } from './plugin';
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
buildQueryContext,
|
||||
normalizeOrderBy,
|
||||
QueryFormData,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
export default function buildQuery(formData: QueryFormData) {
|
||||
return buildQueryContext(formData, baseQueryObject => [
|
||||
{
|
||||
...baseQueryObject,
|
||||
orderby: normalizeOrderBy(baseQueryObject).orderby,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
ControlPanelConfig,
|
||||
getStandardizedControls,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { appCodeControlSetItem } from './controls/appCode';
|
||||
import { dependenciesControlSetItem } from './controls/dependencies';
|
||||
import {
|
||||
layoutControlSetItem,
|
||||
showNavigatorControlSetItem,
|
||||
templateControlSetItem,
|
||||
} from './controls/options';
|
||||
|
||||
const config: ControlPanelConfig = {
|
||||
controlPanelSections: [
|
||||
{
|
||||
label: t('Query'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['groupby'],
|
||||
['metrics'],
|
||||
['adhoc_filters'],
|
||||
['row_limit'],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('App'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
[templateControlSetItem],
|
||||
[appCodeControlSetItem],
|
||||
[dependenciesControlSetItem],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('Display'),
|
||||
expanded: false,
|
||||
controlSetRows: [[layoutControlSetItem], [showNavigatorControlSetItem]],
|
||||
},
|
||||
],
|
||||
formDataOverrides: formData => ({
|
||||
...formData,
|
||||
groupby: getStandardizedControls().popAllColumns(),
|
||||
metrics: getStandardizedControls().popAllMetrics(),
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
ControlSetItem,
|
||||
CustomControlConfig,
|
||||
sharedControls,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { validateNonEmpty } from '@superset-ui/core';
|
||||
import { useTheme } from '@apache-superset/core/theme';
|
||||
import { InfoTooltip } from '@superset-ui/core/components';
|
||||
import { CodeEditor } from '../../components/CodeEditor/CodeEditor';
|
||||
import { ControlHeader } from '../../components/ControlHeader/controlHeader';
|
||||
import { debounceFunc, DEFAULT_APP_CODE } from '../../consts';
|
||||
|
||||
interface AppCodeControlProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
const AppCodeControl = (props: CustomControlConfig<AppCodeControlProps>) => {
|
||||
const theme = useTheme();
|
||||
const val = String(
|
||||
props?.value ? props?.value : props?.default ? props?.default : '',
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ControlHeader>
|
||||
<div>
|
||||
{typeof props.label === 'function' ? null : props.label}
|
||||
<InfoTooltip
|
||||
iconStyle={{ marginLeft: theme.sizeUnit }}
|
||||
tooltip={t(
|
||||
'Query results are exposed to the app as `./data.json`. ' +
|
||||
'Import it from your entry file to render the data.',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</ControlHeader>
|
||||
<CodeEditor
|
||||
theme="dark"
|
||||
mode="javascript"
|
||||
value={val}
|
||||
onChange={source => {
|
||||
debounceFunc(props.onChange, source || '');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const appCodeControlSetItem: ControlSetItem = {
|
||||
name: 'appCode',
|
||||
config: {
|
||||
...sharedControls.entity,
|
||||
type: AppCodeControl,
|
||||
label: t('App Code'),
|
||||
description: t(
|
||||
'Source code for the Sandpack app entry file. The query result is ' +
|
||||
'available as `./data.json`.',
|
||||
),
|
||||
default: DEFAULT_APP_CODE,
|
||||
isInt: false,
|
||||
renderTrigger: true,
|
||||
valueKey: null,
|
||||
validators: [validateNonEmpty],
|
||||
mapStateToProps: ({ form_data }) => ({
|
||||
value: form_data?.appCode ?? form_data?.app_code,
|
||||
}),
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
ControlSetItem,
|
||||
CustomControlConfig,
|
||||
sharedControls,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { CodeEditor } from '../../components/CodeEditor/CodeEditor';
|
||||
import { ControlHeader } from '../../components/ControlHeader/controlHeader';
|
||||
import { debounceFunc, DEFAULT_DEPENDENCIES } from '../../consts';
|
||||
|
||||
interface DependenciesControlProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
const DependenciesControl = (
|
||||
props: CustomControlConfig<DependenciesControlProps>,
|
||||
) => (
|
||||
<div>
|
||||
<ControlHeader>
|
||||
<div>{typeof props.label === 'function' ? null : props.label}</div>
|
||||
</ControlHeader>
|
||||
<CodeEditor
|
||||
theme="dark"
|
||||
mode="json"
|
||||
value={props.value || ''}
|
||||
onChange={source => {
|
||||
debounceFunc(props.onChange, source || '');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const dependenciesControlSetItem: ControlSetItem = {
|
||||
name: 'dependencies',
|
||||
config: {
|
||||
...sharedControls.entity,
|
||||
type: DependenciesControl,
|
||||
label: t('NPM Dependencies'),
|
||||
description: t(
|
||||
'JSON object of npm packages and versions made available to the app.',
|
||||
),
|
||||
default: DEFAULT_DEPENDENCIES,
|
||||
isInt: false,
|
||||
renderTrigger: true,
|
||||
valueKey: null,
|
||||
validators: [],
|
||||
mapStateToProps: ({ form_data }) => ({
|
||||
value: form_data?.dependencies,
|
||||
}),
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { ControlSetItem } from '@superset-ui/chart-controls';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
|
||||
export const templateControlSetItem: ControlSetItem = {
|
||||
name: 'template',
|
||||
config: {
|
||||
type: 'SelectControl',
|
||||
label: t('Sandpack Template'),
|
||||
description: t('Sandpack runtime template used to bundle the app.'),
|
||||
default: 'react',
|
||||
renderTrigger: true,
|
||||
clearable: false,
|
||||
choices: [
|
||||
['react', t('React (JS)')],
|
||||
['react-ts', t('React (TypeScript)')],
|
||||
['vanilla', t('Vanilla JS')],
|
||||
['vanilla-ts', t('Vanilla TypeScript')],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const layoutControlSetItem: ControlSetItem = {
|
||||
name: 'layout',
|
||||
config: {
|
||||
type: 'SelectControl',
|
||||
label: t('Layout'),
|
||||
description: t('Which Sandpack panes to show inside the chart frame.'),
|
||||
default: 'preview',
|
||||
renderTrigger: true,
|
||||
clearable: false,
|
||||
choices: [
|
||||
['preview', t('Preview only')],
|
||||
['split', t('Editor + preview')],
|
||||
['editor', t('Editor only')],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const showNavigatorControlSetItem: ControlSetItem = {
|
||||
name: 'showNavigator',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Show navigator'),
|
||||
description: t('Display the Sandpack URL/navigator bar above the preview.'),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { Behavior, ChartMetadata, ChartPlugin } from '@superset-ui/core';
|
||||
import thumbnail from '../images/thumbnail.png';
|
||||
import thumbnailDark from '../images/thumbnail-dark.png';
|
||||
import buildQuery from './buildQuery';
|
||||
import controlPanel from './controlPanel';
|
||||
import transformProps from './transformProps';
|
||||
|
||||
export default class SandpackChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
const metadata = new ChartMetadata({
|
||||
description: t(
|
||||
'Render a Sandpack-powered React or JS app against the query result. ' +
|
||||
'The dataset is exposed to the app as `./data.json`.',
|
||||
),
|
||||
name: t('Sandpack App'),
|
||||
tags: [t('Advanced'), t('Custom'), t('Experimental')],
|
||||
thumbnail,
|
||||
thumbnailDark,
|
||||
behaviors: [Behavior.InteractiveChart],
|
||||
});
|
||||
|
||||
super({
|
||||
buildQuery,
|
||||
controlPanel,
|
||||
loadChart: () => import('../SandpackChart'),
|
||||
metadata,
|
||||
transformProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { ChartProps, TimeseriesDataRecord } from '@superset-ui/core';
|
||||
|
||||
export default function transformProps(chartProps: ChartProps) {
|
||||
const { width, height, formData, queriesData } = chartProps;
|
||||
const data = queriesData[0].data as TimeseriesDataRecord[];
|
||||
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
data,
|
||||
formData,
|
||||
};
|
||||
}
|
||||
54
superset-frontend/plugins/plugin-chart-sandpack/src/types.ts
Normal file
54
superset-frontend/plugins/plugin-chart-sandpack/src/types.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
QueryFormData,
|
||||
QueryFormMetric,
|
||||
TimeseriesDataRecord,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
export type SandpackTemplate = 'react' | 'react-ts' | 'vanilla' | 'vanilla-ts';
|
||||
|
||||
export type SandpackLayout = 'preview' | 'split' | 'editor';
|
||||
|
||||
export interface SandpackStylesProps {
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
interface SandpackCustomizeProps {
|
||||
appCode?: string;
|
||||
dependencies?: string;
|
||||
template?: SandpackTemplate;
|
||||
layout?: SandpackLayout;
|
||||
showNavigator?: boolean;
|
||||
}
|
||||
|
||||
export type SandpackQueryFormData = QueryFormData &
|
||||
SandpackStylesProps &
|
||||
SandpackCustomizeProps & {
|
||||
metrics?: QueryFormMetric[] | null;
|
||||
groupby?: QueryFormMetric[] | null;
|
||||
all_columns?: QueryFormMetric[] | null;
|
||||
};
|
||||
|
||||
export type SandpackProps = SandpackStylesProps &
|
||||
SandpackCustomizeProps & {
|
||||
data: TimeseriesDataRecord[];
|
||||
formData: SandpackQueryFormData;
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { SandpackChartPlugin } from '../src';
|
||||
|
||||
describe('@superset-ui/plugin-chart-sandpack', () => {
|
||||
test('exists', () => {
|
||||
expect(SandpackChartPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib",
|
||||
"rootDir": "src",
|
||||
"declarationDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
}
|
||||
23
superset-frontend/plugins/plugin-chart-sandpack/types/external.d.ts
vendored
Normal file
23
superset-frontend/plugins/plugin-chart-sandpack/types/external.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
declare module '*.png' {
|
||||
const value: any;
|
||||
export default value;
|
||||
}
|
||||
declare module '*.jpg';
|
||||
@@ -85,6 +85,7 @@ import {
|
||||
} from 'src/chartCustomizations/components';
|
||||
import { PivotTableChartPlugin as PivotTableChartPluginV2 } from '@superset-ui/plugin-chart-pivot-table';
|
||||
import { HandlebarsChartPlugin } from '@superset-ui/plugin-chart-handlebars';
|
||||
import { SandpackChartPlugin } from '@superset-ui/plugin-chart-sandpack';
|
||||
import { ChartCustomizationPlugins, FilterPlugins } from 'src/constants';
|
||||
import AgGridTableChartPlugin from '@superset-ui/plugin-chart-ag-grid-table';
|
||||
import TimeTableChartPlugin from '../TimeTable';
|
||||
@@ -195,6 +196,7 @@ export default class MainPreset extends Preset {
|
||||
new EchartsTreeChartPlugin().configure({ key: VizType.Tree }),
|
||||
new EchartsSunburstChartPlugin().configure({ key: VizType.Sunburst }),
|
||||
new HandlebarsChartPlugin().configure({ key: VizType.Handlebars }),
|
||||
new SandpackChartPlugin().configure({ key: VizType.Sandpack }),
|
||||
new EchartsBubbleChartPlugin().configure({ key: VizType.Bubble }),
|
||||
new CartodiagramPlugin({
|
||||
defaultLayers: [
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
{ "path": "./plugins/plugin-chart-echarts" },
|
||||
{ "path": "./plugins/plugin-chart-point-cluster-map" },
|
||||
{ "path": "./plugins/plugin-chart-handlebars" },
|
||||
{ "path": "./plugins/plugin-chart-sandpack" },
|
||||
{ "path": "./plugins/plugin-chart-pivot-table" },
|
||||
{ "path": "./plugins/plugin-chart-table" },
|
||||
{ "path": "./plugins/plugin-chart-word-cloud" }
|
||||
|
||||
@@ -96,6 +96,8 @@ Available Resources:
|
||||
Available Prompts:
|
||||
- quickstart: Interactive guide for getting started with the MCP service
|
||||
- create_chart_guided: Step-by-step chart creation wizard
|
||||
- create_sandpack_app: Guidance for authoring chart_type="sandpack" apps
|
||||
(./data.json contract, template/dependencies, common patterns)
|
||||
|
||||
IMPORTANT - Using Saved Metrics vs Columns:
|
||||
When get_dataset_info returns a dataset, it includes both 'columns' and 'metrics'.
|
||||
@@ -217,6 +219,16 @@ Chart Types You Can CREATE with generate_chart/generate_explore_link:
|
||||
Requires handlebars_template with Handlebars HTML template string.
|
||||
Supports query_mode="aggregate" (with metrics/groupby) or "raw" (with columns).
|
||||
Data available as {{{{data}}}} array; helpers: dateFormat, formatNumber, stringify.
|
||||
- chart_type="sandpack": In-browser Sandpack app rendered against the query result.
|
||||
Requires app_code (entry-file source). Inside the sandbox the dataset is exposed
|
||||
as ./data.json — import it with `import data from './data.json';`.
|
||||
Pick template: 'react' (default, JS, entry App.js with default export),
|
||||
'react-ts' (TS, App.tsx), 'vanilla' (index.js), or 'vanilla-ts' (index.ts).
|
||||
Add npm packages via dependencies={{...}} (e.g. {{"recharts": "^2.12.0"}}).
|
||||
Defaults to query_mode="raw" with columns; switch to "aggregate" with metrics
|
||||
for grouped data. Use this when the user wants a custom interactive UI that
|
||||
the built-in chart types can't express. Call get_chart_type_schema(chart_type
|
||||
="sandpack") for the full schema and runnable examples.
|
||||
|
||||
Time grain for temporal x-axis (time_grain parameter):
|
||||
- PT1H (hourly), P1D (daily), P1W (weekly), P1M (monthly), P1Y (yearly)
|
||||
|
||||
@@ -37,6 +37,7 @@ from superset.mcp_service.chart.schemas import (
|
||||
MixedTimeseriesChartConfig,
|
||||
PieChartConfig,
|
||||
PivotTableChartConfig,
|
||||
SandpackChartConfig,
|
||||
TableChartConfig,
|
||||
XYChartConfig,
|
||||
)
|
||||
@@ -315,6 +316,7 @@ def map_config_to_form_data(
|
||||
| PivotTableChartConfig
|
||||
| MixedTimeseriesChartConfig
|
||||
| HandlebarsChartConfig
|
||||
| SandpackChartConfig
|
||||
| BigNumberChartConfig,
|
||||
dataset_id: int | str | None = None,
|
||||
) -> Dict[str, Any]:
|
||||
@@ -331,6 +333,8 @@ def map_config_to_form_data(
|
||||
return map_mixed_timeseries_config(config, dataset_id=dataset_id)
|
||||
elif isinstance(config, HandlebarsChartConfig):
|
||||
return map_handlebars_config(config)
|
||||
elif isinstance(config, SandpackChartConfig):
|
||||
return map_sandpack_config(config)
|
||||
elif isinstance(config, BigNumberChartConfig):
|
||||
if config.show_trendline and config.temporal_column:
|
||||
if not is_column_truly_temporal(config.temporal_column, dataset_id):
|
||||
@@ -824,6 +828,41 @@ def map_handlebars_config(config: HandlebarsChartConfig) -> Dict[str, Any]:
|
||||
return form_data
|
||||
|
||||
|
||||
def map_sandpack_config(config: SandpackChartConfig) -> Dict[str, Any]:
|
||||
"""Map sandpack chart config to Superset form_data.
|
||||
|
||||
Mirrors the camelCase keys the frontend control panel writes:
|
||||
``appCode``, ``dependencies`` (JSON-stringified), ``template``, ``layout``,
|
||||
``showNavigator``.
|
||||
"""
|
||||
form_data: Dict[str, Any] = {
|
||||
"viz_type": "sandpack",
|
||||
"appCode": config.app_code,
|
||||
"template": config.template,
|
||||
"layout": config.layout,
|
||||
"showNavigator": config.show_navigator,
|
||||
"row_limit": config.row_limit,
|
||||
}
|
||||
|
||||
if config.dependencies:
|
||||
form_data["dependencies"] = json.dumps(config.dependencies)
|
||||
|
||||
if config.query_mode == "raw":
|
||||
form_data["query_mode"] = "raw"
|
||||
if config.columns:
|
||||
form_data["all_columns"] = [col.name for col in config.columns]
|
||||
else:
|
||||
form_data["query_mode"] = "aggregate"
|
||||
if config.groupby:
|
||||
form_data["groupby"] = [col.name for col in config.groupby]
|
||||
if config.metrics:
|
||||
form_data["metrics"] = [create_metric_object(col) for col in config.metrics]
|
||||
|
||||
_add_adhoc_filters(form_data, config.filters)
|
||||
|
||||
return form_data
|
||||
|
||||
|
||||
def map_pivot_table_config(config: PivotTableChartConfig) -> Dict[str, Any]:
|
||||
"""Map pivot table config to Superset form_data."""
|
||||
if not config.rows:
|
||||
@@ -1111,6 +1150,17 @@ def _handlebars_chart_what(config: HandlebarsChartConfig) -> str:
|
||||
return "Handlebars Chart"
|
||||
|
||||
|
||||
def _sandpack_chart_what(config: SandpackChartConfig) -> str:
|
||||
"""Build the 'what' portion for a sandpack chart name."""
|
||||
if config.query_mode == "raw" and config.columns:
|
||||
cols = ", ".join(col.name for col in config.columns[:3])
|
||||
return f"Sandpack ({cols})"
|
||||
if config.metrics:
|
||||
metrics = ", ".join(col.name for col in config.metrics[:3])
|
||||
return f"Sandpack ({metrics})"
|
||||
return "Sandpack App"
|
||||
|
||||
|
||||
def _big_number_chart_what(config: BigNumberChartConfig) -> str:
|
||||
"""Build the 'what' portion for a big number chart name.
|
||||
|
||||
@@ -1135,6 +1185,7 @@ def generate_chart_name(
|
||||
| PivotTableChartConfig
|
||||
| MixedTimeseriesChartConfig
|
||||
| HandlebarsChartConfig
|
||||
| SandpackChartConfig
|
||||
| BigNumberChartConfig,
|
||||
dataset_name: str | None = None,
|
||||
) -> str:
|
||||
@@ -1169,6 +1220,9 @@ def generate_chart_name(
|
||||
elif isinstance(config, HandlebarsChartConfig):
|
||||
what = _handlebars_chart_what(config)
|
||||
context = _summarize_filters(getattr(config, "filters", None))
|
||||
elif isinstance(config, SandpackChartConfig):
|
||||
what = _sandpack_chart_what(config)
|
||||
context = _summarize_filters(getattr(config, "filters", None))
|
||||
elif isinstance(config, BigNumberChartConfig):
|
||||
what = _big_number_chart_what(config)
|
||||
context = _summarize_filters(getattr(config, "filters", None))
|
||||
@@ -1203,6 +1257,8 @@ def _resolve_viz_type(config: Any) -> str:
|
||||
return "mixed_timeseries"
|
||||
elif chart_type == "handlebars":
|
||||
return "handlebars"
|
||||
elif chart_type == "sandpack":
|
||||
return "sandpack"
|
||||
elif chart_type == "big_number":
|
||||
show_trendline = getattr(config, "show_trendline", False)
|
||||
temporal_column = getattr(config, "temporal_column", None)
|
||||
@@ -1292,6 +1348,10 @@ def analyze_chart_semantics(chart: Any | None, config: Any) -> ChartSemantics:
|
||||
"Renders data using a custom Handlebars HTML template for "
|
||||
"fully flexible layouts like KPI cards, leaderboards, and reports"
|
||||
),
|
||||
"sandpack": (
|
||||
"Runs an in-browser Sandpack app (React/JS) bundled at view time, "
|
||||
"with the query result available inside the sandbox as ./data.json"
|
||||
),
|
||||
"big_number": (
|
||||
"Displays a key metric with a trendline showing "
|
||||
"how the value changes over time"
|
||||
|
||||
@@ -19,3 +19,4 @@
|
||||
|
||||
# Import to register prompts when module is imported
|
||||
from . import create_chart_guided # noqa: F401
|
||||
from . import create_sandpack_app # noqa: F401
|
||||
|
||||
152
superset/mcp_service/chart/prompts/create_sandpack_app.py
Normal file
152
superset/mcp_service/chart/prompts/create_sandpack_app.py
Normal file
@@ -0,0 +1,152 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Guided prompt for authoring chart_type="sandpack" apps.
|
||||
"""
|
||||
|
||||
from superset_core.mcp.decorators import prompt
|
||||
|
||||
|
||||
@prompt("create_sandpack_app")
|
||||
async def create_sandpack_app_prompt(
|
||||
template: str = "react",
|
||||
) -> str:
|
||||
"""Guidance for writing a Sandpack chart app.
|
||||
|
||||
Args:
|
||||
template: Sandpack template — react, react-ts, vanilla, vanilla-ts.
|
||||
"""
|
||||
valid_templates = ("react", "react-ts", "vanilla", "vanilla-ts")
|
||||
template = template if template in valid_templates else "react"
|
||||
|
||||
entry_file = {
|
||||
"react": "App.js (default export of a React component)",
|
||||
"react-ts": "App.tsx (default export of a React component)",
|
||||
"vanilla": "index.js (top-level script — write to document.body)",
|
||||
"vanilla-ts": "index.ts (top-level script — write to document.body)",
|
||||
}[template]
|
||||
|
||||
return f"""**Authoring a Sandpack Chart**
|
||||
|
||||
Sandpack charts let you ship a small, self-contained app rendered against the
|
||||
query result. The chart frame mounts the Sandpack bundler at view time.
|
||||
|
||||
## The contract — read this first
|
||||
|
||||
1. **Data is exposed as `./data.json`** inside the sandbox. The shape mirrors
|
||||
what the query returns (one row per array element, snake_case-style keys).
|
||||
2. **`app_code` is the entry file**. For `{template}` that file is **{entry_file}**.
|
||||
3. **Pick `query_mode` based on what your app needs**:
|
||||
- `query_mode="raw"` (default for sandpack) → row-level data via `columns`.
|
||||
- `query_mode="aggregate"` → grouped data via `metrics`/`groupby`.
|
||||
4. **Add npm packages with `dependencies`** (a `{{name: semver}}` object) only
|
||||
when you actually `import` them. React/ReactDOM ship with the react templates.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1 — Pick a dataset
|
||||
Call `list_datasets` and `get_dataset_info(dataset_id)`. Note the column names
|
||||
exactly as Superset returns them; `./data.json` will use the same keys.
|
||||
|
||||
### Step 2 — Decide raw vs. aggregate
|
||||
- "Show me a list / a table / row-level details" → `query_mode="raw"`,
|
||||
set `columns: [...]`.
|
||||
- "Show me a chart of grouped numbers" → `query_mode="aggregate"`,
|
||||
set `metrics: [{{"name": "...", "aggregate": "SUM"}}]` and optionally `groupby`.
|
||||
In aggregate mode, metric keys in `./data.json` look like `"SUM(revenue)"`.
|
||||
|
||||
### Step 3 — Write `app_code`
|
||||
Always start with:
|
||||
```js
|
||||
import data from './data.json';
|
||||
```
|
||||
Then default-export a React component (for react/react-ts) or write to
|
||||
`document.body` (vanilla). Keep it one file when you can.
|
||||
|
||||
### Step 4 — Submit via `generate_chart` or `generate_explore_link`
|
||||
```json
|
||||
{{
|
||||
"dataset_id": <id>,
|
||||
"config": {{
|
||||
"chart_type": "sandpack",
|
||||
"template": "{template}",
|
||||
"query_mode": "raw",
|
||||
"columns": [{{"name": "<column>"}}],
|
||||
"app_code": "...",
|
||||
"dependencies": {{"recharts": "^2.12.0"}}
|
||||
}}
|
||||
}}
|
||||
```
|
||||
|
||||
## Worked examples
|
||||
|
||||
### Minimal list (raw mode, react)
|
||||
```js
|
||||
import data from './data.json';
|
||||
|
||||
export default function App() {{
|
||||
return (
|
||||
<ul>
|
||||
{{data.map((row, i) => (
|
||||
<li key={{i}}>{{JSON.stringify(row)}}</li>
|
||||
))}}
|
||||
</ul>
|
||||
);
|
||||
}}
|
||||
```
|
||||
|
||||
### Grouped bar chart (aggregate mode, react + recharts)
|
||||
- `metrics: [{{"name": "revenue", "aggregate": "SUM"}}]`, `groupby: [{{"name": "region"}}]`
|
||||
- `dependencies: {{"recharts": "^2.12.0"}}`
|
||||
```js
|
||||
import data from './data.json';
|
||||
import {{ BarChart, Bar, XAxis, YAxis, Tooltip }} from 'recharts';
|
||||
|
||||
export default function App() {{
|
||||
return (
|
||||
<BarChart width={{500}} height={{300}} data={{data}}>
|
||||
<XAxis dataKey="region" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Bar dataKey="SUM(revenue)" />
|
||||
</BarChart>
|
||||
);
|
||||
}}
|
||||
```
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
- **Wrong key in aggregate mode**: in aggregate mode keys are `"SUM(col)"`,
|
||||
`"COUNT(col)"`, etc. — not the bare column name. Inspect a row first.
|
||||
- **Forgot `import data from './data.json'`**: the bundle still loads, but the
|
||||
app has no data. Always import the JSON.
|
||||
- **Used a package without declaring it**: every imported package needs an entry
|
||||
in `dependencies` (except react/react-dom on react templates).
|
||||
- **Multi-file apps**: only `app_code` is editable from MCP today. If you need
|
||||
multiple files, inline them into one entry file or open the chart in Explore
|
||||
to edit further.
|
||||
- **Heavy bundles**: Sandpack rebundles on every prop change. Avoid pulling in
|
||||
charting kitchen-sink libraries when a small one will do.
|
||||
|
||||
## Verification
|
||||
|
||||
After calling `generate_chart` or `generate_explore_link`, open the URL it
|
||||
returns in a browser. If the preview shows a Sandpack error overlay, fix the
|
||||
`app_code` (syntax, missing dependency, wrong data key) and re-call the tool —
|
||||
the chart's `form_data` is rewritten on each call.
|
||||
"""
|
||||
@@ -1054,6 +1054,137 @@ class HandlebarsChartConfig(UnknownFieldCheckMixin):
|
||||
return self
|
||||
|
||||
|
||||
class SandpackChartConfig(UnknownFieldCheckMixin):
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
|
||||
chart_type: Literal["sandpack"] = Field(
|
||||
...,
|
||||
description=(
|
||||
"Chart type discriminator - MUST be 'sandpack' for in-browser apps "
|
||||
"rendered with the Sandpack bundler. The chart frame mounts a "
|
||||
"Sandpack runtime; the dataset is exposed inside the sandbox as "
|
||||
"`./data.json`. Use this for fully custom interactive UIs (small "
|
||||
"React apps, dashboards-in-a-cell, bespoke widgets) when the "
|
||||
"built-in chart types are too restrictive."
|
||||
),
|
||||
)
|
||||
app_code: str = Field(
|
||||
...,
|
||||
description=(
|
||||
"Source code for the entry file of the Sandpack app. Import the "
|
||||
"query result with `import data from './data.json';` (default-export "
|
||||
"the React component for `react`/`react-ts` templates). Keep it a "
|
||||
"single file when possible — Sandpack rebundles on every edit."
|
||||
),
|
||||
min_length=1,
|
||||
max_length=100000,
|
||||
validation_alias=AliasChoices("app_code", "appCode"),
|
||||
)
|
||||
template: Literal["react", "react-ts", "vanilla", "vanilla-ts"] = Field(
|
||||
"react",
|
||||
description=(
|
||||
"Sandpack runtime template. 'react' = JS React (entry: App.js, "
|
||||
"default export). 'react-ts' = TS React (entry: App.tsx). "
|
||||
"'vanilla' / 'vanilla-ts' = plain JS/TS (entry: index.js / "
|
||||
"index.ts). Pick the one matching the syntax used in app_code."
|
||||
),
|
||||
)
|
||||
dependencies: Dict[str, str] | None = Field(
|
||||
None,
|
||||
description=(
|
||||
"Optional npm dependencies as {name: semver} pairs. React/ReactDOM "
|
||||
"ship with the react templates; only add what your app additionally "
|
||||
"imports (e.g. {'recharts': '^2.12.0'})."
|
||||
),
|
||||
)
|
||||
layout: Literal["preview", "split", "editor"] = Field(
|
||||
"preview",
|
||||
description=(
|
||||
"Which Sandpack panes are visible in the chart frame. 'preview' = "
|
||||
"rendered output only (recommended for finished charts), 'split' = "
|
||||
"editor + preview side-by-side, 'editor' = code only."
|
||||
),
|
||||
)
|
||||
show_navigator: bool = Field(
|
||||
False,
|
||||
description="Show the Sandpack URL bar above the preview pane.",
|
||||
validation_alias=AliasChoices("show_navigator", "showNavigator"),
|
||||
)
|
||||
query_mode: Literal["aggregate", "raw"] = Field(
|
||||
"raw",
|
||||
description=(
|
||||
"Query mode: 'raw' returns individual rows (default for sandpack — "
|
||||
"most apps want row-level data), 'aggregate' groups with metrics."
|
||||
),
|
||||
)
|
||||
columns: list[ColumnRef] | None = Field(
|
||||
None,
|
||||
description=(
|
||||
"Columns to fetch in raw mode (query_mode='raw'). These become the "
|
||||
"fields available on each object in `./data.json`."
|
||||
),
|
||||
)
|
||||
groupby: list[ColumnRef] | None = Field(
|
||||
None,
|
||||
description=(
|
||||
"Group-by columns in aggregate mode (query_mode='aggregate'). "
|
||||
"Become the dimensions for aggregation."
|
||||
),
|
||||
validation_alias=AliasChoices("groupby", "group_by"),
|
||||
)
|
||||
metrics: list[ColumnRef] | None = Field(
|
||||
None,
|
||||
description=(
|
||||
"Metrics to aggregate in aggregate mode. Each must have an "
|
||||
"aggregate function (SUM, COUNT, AVG, ...)."
|
||||
),
|
||||
)
|
||||
filters: list[FilterConfig] | None = Field(
|
||||
None, description="Filters to apply"
|
||||
)
|
||||
row_limit: int = Field(
|
||||
1000,
|
||||
description="Maximum number of rows passed to the app",
|
||||
ge=1,
|
||||
le=50000,
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_query_fields(self) -> "SandpackChartConfig":
|
||||
"""Enforce that the right query fields are present for the chosen mode."""
|
||||
if self.query_mode == "raw":
|
||||
if not self.columns:
|
||||
raise ValueError(
|
||||
"Sandpack chart in 'raw' query mode requires 'columns'. "
|
||||
"Specify which columns to expose in ./data.json."
|
||||
)
|
||||
if self.metrics:
|
||||
raise ValueError(
|
||||
"Sandpack chart in 'raw' query mode does not use 'metrics'. "
|
||||
"Remove 'metrics' or switch to 'aggregate' query mode."
|
||||
)
|
||||
if self.groupby:
|
||||
raise ValueError(
|
||||
"Sandpack chart in 'raw' query mode does not use 'groupby'. "
|
||||
"Remove 'groupby' or switch to 'aggregate' query mode."
|
||||
)
|
||||
else:
|
||||
if not self.metrics:
|
||||
raise ValueError(
|
||||
"Sandpack chart in 'aggregate' query mode requires 'metrics'. "
|
||||
"Specify at least one metric with an aggregate function."
|
||||
)
|
||||
missing_agg = [m.name for m in self.metrics if not m.is_metric]
|
||||
if missing_agg:
|
||||
raise ValueError(
|
||||
f"Sandpack chart in 'aggregate' query mode requires an "
|
||||
f"aggregate function on every metric. Missing aggregate for: "
|
||||
f"{', '.join(missing_agg)}. "
|
||||
f"Use one of: SUM, COUNT, AVG, MIN, MAX, COUNT_DISTINCT, etc."
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
class BigNumberChartConfig(UnknownFieldCheckMixin):
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
|
||||
@@ -1343,13 +1474,14 @@ ChartConfig = Annotated[
|
||||
| PivotTableChartConfig
|
||||
| MixedTimeseriesChartConfig
|
||||
| HandlebarsChartConfig
|
||||
| SandpackChartConfig
|
||||
| BigNumberChartConfig,
|
||||
Field(
|
||||
discriminator="chart_type",
|
||||
description=(
|
||||
"Chart configuration - specify chart_type as 'xy', 'table', "
|
||||
"'pie', 'pivot_table', 'mixed_timeseries', 'handlebars', "
|
||||
"or 'big_number'"
|
||||
"'sandpack', or 'big_number'"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -33,6 +33,7 @@ from superset.mcp_service.chart.schemas import (
|
||||
MixedTimeseriesChartConfig,
|
||||
PieChartConfig,
|
||||
PivotTableChartConfig,
|
||||
SandpackChartConfig,
|
||||
TableChartConfig,
|
||||
XYChartConfig,
|
||||
)
|
||||
@@ -47,6 +48,7 @@ _CHART_TYPE_ADAPTERS: Dict[str, TypeAdapter[Any]] = {
|
||||
"pivot_table": TypeAdapter(PivotTableChartConfig),
|
||||
"mixed_timeseries": TypeAdapter(MixedTimeseriesChartConfig),
|
||||
"handlebars": TypeAdapter(HandlebarsChartConfig),
|
||||
"sandpack": TypeAdapter(SandpackChartConfig),
|
||||
"big_number": TypeAdapter(BigNumberChartConfig),
|
||||
}
|
||||
|
||||
@@ -110,6 +112,49 @@ _CHART_EXAMPLES: Dict[str, list[Dict[str, Any]]] = {
|
||||
"handlebars_template": "{{#each data}}<p>{{customer_name}}</p>{{/each}}",
|
||||
},
|
||||
],
|
||||
"sandpack": [
|
||||
{
|
||||
"chart_type": "sandpack",
|
||||
"template": "react",
|
||||
"query_mode": "raw",
|
||||
"columns": [{"name": "customer_name"}, {"name": "revenue"}],
|
||||
"app_code": (
|
||||
"import data from './data.json';\n\n"
|
||||
"export default function App() {\n"
|
||||
" return (\n"
|
||||
" <ul>\n"
|
||||
" {data.map((row, i) => (\n"
|
||||
" <li key={i}>{row.customer_name}: {row.revenue}</li>\n"
|
||||
" ))}\n"
|
||||
" </ul>\n"
|
||||
" );\n"
|
||||
"}\n"
|
||||
),
|
||||
},
|
||||
{
|
||||
"chart_type": "sandpack",
|
||||
"template": "react",
|
||||
"query_mode": "aggregate",
|
||||
"groupby": [{"name": "region"}],
|
||||
"metrics": [{"name": "revenue", "aggregate": "SUM"}],
|
||||
"dependencies": {"recharts": "^2.12.0"},
|
||||
"app_code": (
|
||||
"import data from './data.json';\n"
|
||||
"import { BarChart, Bar, XAxis, YAxis, Tooltip } "
|
||||
"from 'recharts';\n\n"
|
||||
"export default function App() {\n"
|
||||
" return (\n"
|
||||
" <BarChart width={400} height={300} data={data}>\n"
|
||||
" <XAxis dataKey=\"region\" />\n"
|
||||
" <YAxis />\n"
|
||||
" <Tooltip />\n"
|
||||
" <Bar dataKey=\"SUM(revenue)\" />\n"
|
||||
" </BarChart>\n"
|
||||
" );\n"
|
||||
"}\n"
|
||||
),
|
||||
},
|
||||
],
|
||||
"big_number": [
|
||||
{
|
||||
"chart_type": "big_number",
|
||||
@@ -179,7 +224,7 @@ def get_chart_type_schema(
|
||||
for a chart configuration before calling generate_chart or update_chart.
|
||||
|
||||
Valid chart_type values: xy, table, pie, pivot_table,
|
||||
mixed_timeseries, handlebars, big_number.
|
||||
mixed_timeseries, handlebars, sandpack, big_number.
|
||||
|
||||
Returns the JSON Schema for the requested chart type, optionally
|
||||
with working examples.
|
||||
|
||||
@@ -28,6 +28,7 @@ from superset.mcp_service.chart.schemas import (
|
||||
BigNumberChartConfig,
|
||||
ColumnRef,
|
||||
HandlebarsChartConfig,
|
||||
SandpackChartConfig,
|
||||
MixedTimeseriesChartConfig,
|
||||
PieChartConfig,
|
||||
PivotTableChartConfig,
|
||||
@@ -302,6 +303,13 @@ class DatasetValidator:
|
||||
refs.extend(config.groupby)
|
||||
if config.metrics:
|
||||
refs.extend(config.metrics)
|
||||
elif isinstance(config, SandpackChartConfig):
|
||||
if config.columns:
|
||||
refs.extend(config.columns)
|
||||
if config.groupby:
|
||||
refs.extend(config.groupby)
|
||||
if config.metrics:
|
||||
refs.extend(config.metrics)
|
||||
elif isinstance(config, BigNumberChartConfig):
|
||||
refs.append(config.metric)
|
||||
if config.temporal_column:
|
||||
|
||||
@@ -134,6 +134,8 @@ class SchemaValidator:
|
||||
"Add 'chart_type': 'pivot_table' for interactive pivot tables",
|
||||
"Add 'chart_type': 'mixed_timeseries' for dual-series time charts",
|
||||
"Add 'chart_type': 'handlebars' for custom HTML template charts",
|
||||
"Add 'chart_type': 'sandpack' for in-browser Sandpack apps "
|
||||
"(React/JS) rendered against the query result",
|
||||
"Add 'chart_type': 'big_number' for big number display",
|
||||
"Example: 'config': {'chart_type': 'xy', ...}",
|
||||
],
|
||||
@@ -155,6 +157,7 @@ class SchemaValidator:
|
||||
"pivot_table": SchemaValidator._pre_validate_pivot_table_config,
|
||||
"mixed_timeseries": SchemaValidator._pre_validate_mixed_timeseries_config,
|
||||
"handlebars": SchemaValidator._pre_validate_handlebars_config,
|
||||
"sandpack": SchemaValidator._pre_validate_sandpack_config,
|
||||
"big_number": SchemaValidator._pre_validate_big_number_config,
|
||||
}
|
||||
|
||||
@@ -172,6 +175,7 @@ class SchemaValidator:
|
||||
"Use 'chart_type': 'pivot_table' for interactive pivot tables",
|
||||
"Use 'chart_type': 'mixed_timeseries' for dual-series time charts",
|
||||
"Use 'chart_type': 'handlebars' for custom HTML template charts",
|
||||
"Use 'chart_type': 'sandpack' for Sandpack-rendered React/JS apps",
|
||||
"Use 'chart_type': 'big_number' for big number display",
|
||||
"Check spelling and ensure lowercase",
|
||||
],
|
||||
@@ -358,6 +362,130 @@ class SchemaValidator:
|
||||
|
||||
return True, None
|
||||
|
||||
@staticmethod
|
||||
def _pre_validate_sandpack_config(
|
||||
config: Dict[str, Any],
|
||||
) -> Tuple[bool, ChartGenerationError | None]:
|
||||
"""Pre-validate sandpack chart configuration."""
|
||||
# Accept both snake_case and the camelCase form_data alias.
|
||||
app_code = config.get("app_code") or config.get("appCode")
|
||||
if app_code is None:
|
||||
return False, ChartGenerationError(
|
||||
error_type="missing_app_code",
|
||||
message="Sandpack chart missing required field: app_code",
|
||||
details="Sandpack charts require an 'app_code' string with the "
|
||||
"source code of the entry file. Inside the sandbox the "
|
||||
"dataset is exposed as './data.json'.",
|
||||
suggestions=[
|
||||
"Add 'app_code' with the source for your entry file",
|
||||
"React entry: import data from './data.json'; "
|
||||
"export default function App() { ... }",
|
||||
"Vanilla entry: import data from './data.json'; "
|
||||
"document.body.innerText = JSON.stringify(data);",
|
||||
],
|
||||
error_code="MISSING_APP_CODE",
|
||||
)
|
||||
|
||||
if not isinstance(app_code, str) or not app_code.strip():
|
||||
return False, ChartGenerationError(
|
||||
error_type="invalid_app_code",
|
||||
message="Sandpack app_code must be a non-empty string",
|
||||
details="The 'app_code' field must contain the source code for "
|
||||
"the entry file as a non-empty string.",
|
||||
suggestions=[
|
||||
"Ensure app_code is a non-empty string",
|
||||
"Example: \"import data from './data.json';\\n"
|
||||
"export default function App(){return <pre>{JSON."
|
||||
'stringify(data)}</pre>}"',
|
||||
],
|
||||
error_code="INVALID_APP_CODE",
|
||||
)
|
||||
|
||||
template = config.get("template", "react")
|
||||
valid_templates = ("react", "react-ts", "vanilla", "vanilla-ts")
|
||||
if template not in valid_templates:
|
||||
return False, ChartGenerationError(
|
||||
error_type="invalid_template",
|
||||
message=f"Invalid Sandpack template: '{template}'",
|
||||
details=f"template must be one of: {', '.join(valid_templates)}",
|
||||
suggestions=[
|
||||
"Use 'react' (default) for JS React apps",
|
||||
"Use 'react-ts' for TypeScript React",
|
||||
"Use 'vanilla' or 'vanilla-ts' for plain JS/TS",
|
||||
],
|
||||
error_code="INVALID_SANDPACK_TEMPLATE",
|
||||
)
|
||||
|
||||
dependencies = config.get("dependencies")
|
||||
if dependencies is not None and not isinstance(dependencies, dict):
|
||||
return False, ChartGenerationError(
|
||||
error_type="invalid_dependencies",
|
||||
message="Sandpack dependencies must be an object",
|
||||
details="The 'dependencies' field must be an object mapping "
|
||||
"package names to semver ranges, or omitted entirely.",
|
||||
suggestions=[
|
||||
"Pass an object: {'recharts': '^2.12.0'}",
|
||||
"Or omit 'dependencies' to use only template defaults",
|
||||
],
|
||||
error_code="INVALID_SANDPACK_DEPENDENCIES",
|
||||
)
|
||||
|
||||
layout = config.get("layout", "preview")
|
||||
if layout not in ("preview", "split", "editor"):
|
||||
return False, ChartGenerationError(
|
||||
error_type="invalid_layout",
|
||||
message=f"Invalid Sandpack layout: '{layout}'",
|
||||
details="layout must be 'preview', 'split', or 'editor'",
|
||||
suggestions=[
|
||||
"Use 'preview' for finished charts (default)",
|
||||
"Use 'split' for editor + preview side-by-side",
|
||||
"Use 'editor' to show only the code",
|
||||
],
|
||||
error_code="INVALID_SANDPACK_LAYOUT",
|
||||
)
|
||||
|
||||
query_mode = config.get("query_mode", "raw")
|
||||
if query_mode not in ("aggregate", "raw"):
|
||||
return False, ChartGenerationError(
|
||||
error_type="invalid_query_mode",
|
||||
message="Invalid query_mode for sandpack chart",
|
||||
details="query_mode must be either 'aggregate' or 'raw'",
|
||||
suggestions=[
|
||||
"Use 'raw' for row-level data (default for sandpack)",
|
||||
"Use 'aggregate' with 'metrics' for grouped data",
|
||||
],
|
||||
error_code="INVALID_QUERY_MODE",
|
||||
)
|
||||
|
||||
if query_mode == "raw" and not config.get("columns"):
|
||||
return False, ChartGenerationError(
|
||||
error_type="missing_raw_columns",
|
||||
message="Sandpack chart in 'raw' mode requires 'columns'",
|
||||
details="When query_mode is 'raw', you must specify which "
|
||||
"columns to include in ./data.json",
|
||||
suggestions=[
|
||||
"Add 'columns': [{'name': 'column_name'}]",
|
||||
"Or use query_mode='aggregate' with 'metrics' "
|
||||
"and optional 'groupby'",
|
||||
],
|
||||
error_code="MISSING_RAW_COLUMNS",
|
||||
)
|
||||
|
||||
if query_mode == "aggregate" and not config.get("metrics"):
|
||||
return False, ChartGenerationError(
|
||||
error_type="missing_aggregate_metrics",
|
||||
message="Sandpack chart in 'aggregate' mode requires 'metrics'",
|
||||
details="When query_mode is 'aggregate', specify at least one "
|
||||
"metric with an aggregate function",
|
||||
suggestions=[
|
||||
"Add 'metrics': [{'name': 'col', 'aggregate': 'SUM'}]",
|
||||
"Or use query_mode='raw' with 'columns' for individual rows",
|
||||
],
|
||||
error_code="MISSING_AGGREGATE_METRICS",
|
||||
)
|
||||
|
||||
return True, None
|
||||
|
||||
@staticmethod
|
||||
def _pre_validate_big_number_config(
|
||||
config: Dict[str, Any],
|
||||
@@ -591,6 +719,26 @@ class SchemaValidator:
|
||||
],
|
||||
error_code="HANDLEBARS_VALIDATION_ERROR",
|
||||
)
|
||||
elif chart_type == "sandpack":
|
||||
return ChartGenerationError(
|
||||
error_type="sandpack_validation_error",
|
||||
message="Sandpack chart configuration validation failed",
|
||||
details="The sandpack chart configuration is missing "
|
||||
"required fields or has invalid structure",
|
||||
suggestions=[
|
||||
"Ensure 'app_code' is a non-empty string",
|
||||
"Pick a template: 'react' (default), 'react-ts', "
|
||||
"'vanilla', or 'vanilla-ts'",
|
||||
"For raw mode (default): set 'columns'",
|
||||
"For aggregate mode: set 'metrics' (and optionally "
|
||||
"'groupby') and switch 'query_mode' to 'aggregate'",
|
||||
"Example: {'chart_type': 'sandpack', 'app_code': "
|
||||
"\"import data from './data.json'; export default "
|
||||
"() => <pre>{JSON.stringify(data)}</pre>\", "
|
||||
"'columns': [{'name': 'product'}]}",
|
||||
],
|
||||
error_code="SANDPACK_VALIDATION_ERROR",
|
||||
)
|
||||
elif chart_type == "big_number":
|
||||
return ChartGenerationError(
|
||||
error_type="big_number_validation_error",
|
||||
|
||||
423
tests/unit_tests/mcp_service/chart/test_sandpack_chart.py
Normal file
423
tests/unit_tests/mcp_service/chart/test_sandpack_chart.py
Normal file
@@ -0,0 +1,423 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Tests for Sandpack chart type support in MCP service."""
|
||||
|
||||
import pytest
|
||||
|
||||
from superset.mcp_service.chart.chart_utils import (
|
||||
_resolve_viz_type,
|
||||
analyze_chart_capabilities,
|
||||
analyze_chart_semantics,
|
||||
generate_chart_name,
|
||||
map_config_to_form_data,
|
||||
map_sandpack_config,
|
||||
)
|
||||
from superset.mcp_service.chart.schemas import (
|
||||
ColumnRef,
|
||||
FilterConfig,
|
||||
SandpackChartConfig,
|
||||
)
|
||||
from superset.mcp_service.chart.validation.schema_validator import SchemaValidator
|
||||
from superset.utils import json
|
||||
|
||||
DEFAULT_APP = "import data from './data.json';\nexport default () => null;\n"
|
||||
|
||||
|
||||
class TestSandpackChartConfig:
|
||||
def test_minimal_raw_config(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
columns=[ColumnRef(name="product")],
|
||||
)
|
||||
assert config.chart_type == "sandpack"
|
||||
assert config.template == "react"
|
||||
assert config.layout == "preview"
|
||||
assert config.query_mode == "raw"
|
||||
assert config.row_limit == 1000
|
||||
|
||||
def test_minimal_aggregate_config(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
query_mode="aggregate",
|
||||
metrics=[ColumnRef(name="sales", aggregate="SUM")],
|
||||
)
|
||||
assert config.query_mode == "aggregate"
|
||||
|
||||
def test_app_code_alias(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
appCode=DEFAULT_APP,
|
||||
columns=[ColumnRef(name="product")],
|
||||
)
|
||||
assert config.app_code == DEFAULT_APP
|
||||
|
||||
def test_show_navigator_alias(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
columns=[ColumnRef(name="product")],
|
||||
showNavigator=True,
|
||||
)
|
||||
assert config.show_navigator is True
|
||||
|
||||
def test_app_code_min_length(self) -> None:
|
||||
with pytest.raises(ValueError, match="at least 1 character"):
|
||||
SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code="",
|
||||
columns=[ColumnRef(name="product")],
|
||||
)
|
||||
|
||||
def test_raw_mode_requires_columns(self) -> None:
|
||||
with pytest.raises(ValueError, match="requires 'columns'"):
|
||||
SandpackChartConfig(chart_type="sandpack", app_code=DEFAULT_APP)
|
||||
|
||||
def test_aggregate_mode_requires_metrics(self) -> None:
|
||||
with pytest.raises(ValueError, match="requires 'metrics'"):
|
||||
SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
query_mode="aggregate",
|
||||
)
|
||||
|
||||
def test_aggregate_mode_requires_aggregate_function(self) -> None:
|
||||
with pytest.raises(ValueError, match="Missing aggregate for"):
|
||||
SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
query_mode="aggregate",
|
||||
metrics=[ColumnRef(name="sales")],
|
||||
)
|
||||
|
||||
def test_raw_mode_rejects_metrics(self) -> None:
|
||||
with pytest.raises(ValueError, match="does not use 'metrics'"):
|
||||
SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
query_mode="raw",
|
||||
columns=[ColumnRef(name="product")],
|
||||
metrics=[ColumnRef(name="sales", aggregate="SUM")],
|
||||
)
|
||||
|
||||
def test_extra_fields_forbidden(self) -> None:
|
||||
with pytest.raises(ValueError, match="Unknown field"):
|
||||
SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
columns=[ColumnRef(name="product")],
|
||||
bogus_field="x",
|
||||
)
|
||||
|
||||
def test_invalid_template_rejected_by_pydantic(self) -> None:
|
||||
with pytest.raises(ValueError):
|
||||
SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
template="vue", # not a valid Literal value
|
||||
columns=[ColumnRef(name="product")],
|
||||
)
|
||||
|
||||
|
||||
class TestMapSandpackConfig:
|
||||
def test_raw_mode_basic(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
columns=[ColumnRef(name="product"), ColumnRef(name="price")],
|
||||
)
|
||||
result = map_sandpack_config(config)
|
||||
|
||||
assert result["viz_type"] == "sandpack"
|
||||
assert result["appCode"] == DEFAULT_APP
|
||||
assert result["template"] == "react"
|
||||
assert result["layout"] == "preview"
|
||||
assert result["showNavigator"] is False
|
||||
assert result["query_mode"] == "raw"
|
||||
assert result["all_columns"] == ["product", "price"]
|
||||
assert "metrics" not in result
|
||||
assert "groupby" not in result
|
||||
|
||||
def test_aggregate_mode_with_groupby(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
query_mode="aggregate",
|
||||
groupby=[ColumnRef(name="region")],
|
||||
metrics=[ColumnRef(name="sales", aggregate="SUM")],
|
||||
)
|
||||
result = map_sandpack_config(config)
|
||||
|
||||
assert result["query_mode"] == "aggregate"
|
||||
assert result["groupby"] == ["region"]
|
||||
assert result["metrics"][0]["aggregate"] == "SUM"
|
||||
|
||||
def test_dependencies_serialized_to_json_string(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
columns=[ColumnRef(name="product")],
|
||||
dependencies={"recharts": "^2.12.0"},
|
||||
)
|
||||
result = map_sandpack_config(config)
|
||||
|
||||
assert isinstance(result["dependencies"], str)
|
||||
assert json.loads(result["dependencies"]) == {"recharts": "^2.12.0"}
|
||||
|
||||
def test_no_dependencies_omits_key(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
columns=[ColumnRef(name="product")],
|
||||
)
|
||||
assert "dependencies" not in map_sandpack_config(config)
|
||||
|
||||
def test_with_filters(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
columns=[ColumnRef(name="product")],
|
||||
filters=[FilterConfig(column="status", op="=", value="active")],
|
||||
)
|
||||
result = map_sandpack_config(config)
|
||||
|
||||
assert "adhoc_filters" in result
|
||||
assert result["adhoc_filters"][0]["subject"] == "status"
|
||||
assert result["adhoc_filters"][0]["comparator"] == "active"
|
||||
|
||||
def test_layout_and_navigator_pass_through(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
columns=[ColumnRef(name="product")],
|
||||
layout="split",
|
||||
show_navigator=True,
|
||||
)
|
||||
result = map_sandpack_config(config)
|
||||
assert result["layout"] == "split"
|
||||
assert result["showNavigator"] is True
|
||||
|
||||
|
||||
class TestMapConfigToFormDataSandpack:
|
||||
def test_dispatches_sandpack_config(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
columns=[ColumnRef(name="product")],
|
||||
)
|
||||
result = map_config_to_form_data(config)
|
||||
assert result["viz_type"] == "sandpack"
|
||||
|
||||
|
||||
class TestGenerateChartNameSandpack:
|
||||
def test_raw_mode_uses_columns(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
columns=[ColumnRef(name="product"), ColumnRef(name="price")],
|
||||
)
|
||||
name = generate_chart_name(config)
|
||||
assert "Sandpack" in name
|
||||
assert "product" in name
|
||||
|
||||
def test_aggregate_mode_uses_metrics(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
query_mode="aggregate",
|
||||
metrics=[ColumnRef(name="sales", aggregate="SUM")],
|
||||
)
|
||||
name = generate_chart_name(config)
|
||||
assert "Sandpack" in name
|
||||
assert "sales" in name
|
||||
|
||||
|
||||
class TestResolveVizTypeSandpack:
|
||||
def test_sandpack(self) -> None:
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
config = MagicMock(chart_type="sandpack")
|
||||
assert _resolve_viz_type(config) == "sandpack"
|
||||
|
||||
|
||||
class TestAnalyzeChartCapabilitiesSandpack:
|
||||
def test_sandpack_capabilities(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
columns=[ColumnRef(name="product")],
|
||||
)
|
||||
caps = analyze_chart_capabilities(None, config)
|
||||
|
||||
assert caps.supports_export is True
|
||||
assert "url" in caps.optimal_formats
|
||||
|
||||
|
||||
class TestAnalyzeChartSemanticsSandpack:
|
||||
def test_sandpack_semantics(self) -> None:
|
||||
config = SandpackChartConfig(
|
||||
chart_type="sandpack",
|
||||
app_code=DEFAULT_APP,
|
||||
columns=[ColumnRef(name="product")],
|
||||
)
|
||||
semantics = analyze_chart_semantics(None, config)
|
||||
assert semantics.primary_insight is not None
|
||||
|
||||
|
||||
class TestSchemaValidatorSandpack:
|
||||
def test_minimal_raw_accepted(self) -> None:
|
||||
data = {
|
||||
"dataset_id": 1,
|
||||
"config": {
|
||||
"chart_type": "sandpack",
|
||||
"app_code": DEFAULT_APP,
|
||||
"columns": [{"name": "product"}],
|
||||
},
|
||||
}
|
||||
is_valid, request, error = SchemaValidator.validate_request(data)
|
||||
assert is_valid is True
|
||||
assert request is not None
|
||||
assert error is None
|
||||
|
||||
def test_camelcase_app_code_alias(self) -> None:
|
||||
data = {
|
||||
"dataset_id": 1,
|
||||
"config": {
|
||||
"chart_type": "sandpack",
|
||||
"appCode": DEFAULT_APP,
|
||||
"columns": [{"name": "product"}],
|
||||
},
|
||||
}
|
||||
is_valid, _, error = SchemaValidator.validate_request(data)
|
||||
assert is_valid is True
|
||||
assert error is None
|
||||
|
||||
def test_missing_app_code(self) -> None:
|
||||
data = {
|
||||
"dataset_id": 1,
|
||||
"config": {
|
||||
"chart_type": "sandpack",
|
||||
"columns": [{"name": "product"}],
|
||||
},
|
||||
}
|
||||
is_valid, _, error = SchemaValidator.validate_request(data)
|
||||
assert is_valid is False
|
||||
assert error is not None
|
||||
assert error.error_code == "MISSING_APP_CODE"
|
||||
|
||||
def test_empty_app_code(self) -> None:
|
||||
data = {
|
||||
"dataset_id": 1,
|
||||
"config": {
|
||||
"chart_type": "sandpack",
|
||||
"app_code": " ",
|
||||
"columns": [{"name": "product"}],
|
||||
},
|
||||
}
|
||||
is_valid, _, error = SchemaValidator.validate_request(data)
|
||||
assert is_valid is False
|
||||
assert error is not None
|
||||
assert error.error_code == "INVALID_APP_CODE"
|
||||
|
||||
def test_invalid_template(self) -> None:
|
||||
data = {
|
||||
"dataset_id": 1,
|
||||
"config": {
|
||||
"chart_type": "sandpack",
|
||||
"app_code": DEFAULT_APP,
|
||||
"template": "svelte",
|
||||
"columns": [{"name": "product"}],
|
||||
},
|
||||
}
|
||||
is_valid, _, error = SchemaValidator.validate_request(data)
|
||||
assert is_valid is False
|
||||
assert error is not None
|
||||
assert error.error_code == "INVALID_SANDPACK_TEMPLATE"
|
||||
|
||||
def test_dependencies_must_be_object(self) -> None:
|
||||
data = {
|
||||
"dataset_id": 1,
|
||||
"config": {
|
||||
"chart_type": "sandpack",
|
||||
"app_code": DEFAULT_APP,
|
||||
"dependencies": ["recharts"],
|
||||
"columns": [{"name": "product"}],
|
||||
},
|
||||
}
|
||||
is_valid, _, error = SchemaValidator.validate_request(data)
|
||||
assert is_valid is False
|
||||
assert error is not None
|
||||
assert error.error_code == "INVALID_SANDPACK_DEPENDENCIES"
|
||||
|
||||
def test_invalid_layout(self) -> None:
|
||||
data = {
|
||||
"dataset_id": 1,
|
||||
"config": {
|
||||
"chart_type": "sandpack",
|
||||
"app_code": DEFAULT_APP,
|
||||
"layout": "fullscreen",
|
||||
"columns": [{"name": "product"}],
|
||||
},
|
||||
}
|
||||
is_valid, _, error = SchemaValidator.validate_request(data)
|
||||
assert is_valid is False
|
||||
assert error is not None
|
||||
assert error.error_code == "INVALID_SANDPACK_LAYOUT"
|
||||
|
||||
def test_raw_mode_missing_columns(self) -> None:
|
||||
data = {
|
||||
"dataset_id": 1,
|
||||
"config": {
|
||||
"chart_type": "sandpack",
|
||||
"app_code": DEFAULT_APP,
|
||||
},
|
||||
}
|
||||
is_valid, _, error = SchemaValidator.validate_request(data)
|
||||
assert is_valid is False
|
||||
assert error is not None
|
||||
assert error.error_code == "MISSING_RAW_COLUMNS"
|
||||
|
||||
def test_aggregate_mode_missing_metrics(self) -> None:
|
||||
data = {
|
||||
"dataset_id": 1,
|
||||
"config": {
|
||||
"chart_type": "sandpack",
|
||||
"app_code": DEFAULT_APP,
|
||||
"query_mode": "aggregate",
|
||||
},
|
||||
}
|
||||
is_valid, _, error = SchemaValidator.validate_request(data)
|
||||
assert is_valid is False
|
||||
assert error is not None
|
||||
assert error.error_code == "MISSING_AGGREGATE_METRICS"
|
||||
|
||||
def test_invalid_chart_type_suggestions_mention_sandpack(self) -> None:
|
||||
data = {"dataset_id": 1, "config": {"chart_type": "invalid"}}
|
||||
is_valid, _, error = SchemaValidator.validate_request(data)
|
||||
assert is_valid is False
|
||||
assert error is not None
|
||||
assert "sandpack" in (error.details or "")
|
||||
|
||||
def test_missing_chart_type_suggestions_mention_sandpack(self) -> None:
|
||||
data = {"dataset_id": 1, "config": {}}
|
||||
is_valid, _, error = SchemaValidator.validate_request(data)
|
||||
assert is_valid is False
|
||||
assert error is not None
|
||||
suggestions_text = " ".join(error.suggestions or [])
|
||||
assert "sandpack" in suggestions_text
|
||||
@@ -94,9 +94,10 @@ class TestGetChartTypeSchema:
|
||||
assert example["chart_type"] == "pie"
|
||||
|
||||
def test_valid_chart_types_constant(self) -> None:
|
||||
assert len(VALID_CHART_TYPES) == 7
|
||||
assert len(VALID_CHART_TYPES) == 8
|
||||
assert "xy" in VALID_CHART_TYPES
|
||||
assert "table" in VALID_CHART_TYPES
|
||||
assert "sandpack" in VALID_CHART_TYPES
|
||||
|
||||
def test_all_chart_types_have_examples(self) -> None:
|
||||
for chart_type in VALID_CHART_TYPES:
|
||||
|
||||
Reference in New Issue
Block a user