Compare commits

...

2 Commits

Author SHA1 Message Date
Evan
bd544b8bcb fix: remove stale vm-browserify reference in storybook webpack config
vm-browserify was dropped from package.json alongside vm2, but
.storybook/main.mjs still called require.resolve('vm-browserify'),
breaking the storybook build. Mirror the vm: false fallback already
used in webpack.config.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 02:24:23 -07:00
Evan Rusackas
79e09ec1b3 chore: remove deck.gl JavaScript tooltip controls and ENABLE_JAVASCRIPT_CONTROLS
Removes the deck.gl JS tooltip/onclick-href/data-mutator controls and the
GeoJSON layer's label/icon JavaScript-mode generators, along with the
ENABLE_JAVASCRIPT_CONTROLS feature flag that gated them (closes #41045).
These let users write arbitrary JS, sandboxed via Node's vm module, to
customize deck.gl tooltips/click behavior/data transforms; the flag
defaulted off and saw negligible use.

Removes the now-dead sandboxedEval() runtime (utils/sandbox.ts), its
vm-browserify webpack polyfill and devDependency, and the underscore
dependency it alone pulled into the deck.gl plugin. Cleans up the
associated backend form_data-stripping mechanism, tests, fixtures, and
docs for the removed feature.

vm2 (the dependency this was originally suspected to pull in) turned out
to be unrelated: it was only a transitive dev-dependency of the
Cartodiagram plugin's geostyler dependency via typescript-json-schema,
which has since dropped it upstream (>=0.68.0). Forced that resolution
via an npm override, confirmed geostyler ships no code that touches
typescript-json-schema at runtime, and verified the Cartodiagram plugin
still builds and its full test suite passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 22:24:24 -07:00
51 changed files with 206 additions and 871 deletions

View File

@@ -24,6 +24,19 @@ assists people when migrating to a new version.
## Next
### Removed deck.gl JavaScript tooltip/data-mutator controls and ENABLE_JAVASCRIPT_CONTROLS
The `ENABLE_JAVASCRIPT_CONTROLS` feature flag and the deck.gl chart controls it gated
(`js_tooltip`, `js_onclick_href`, `js_data_mutator`, and the GeoJSON layer's label/icon
JavaScript-mode generators) have been removed. These controls let users write arbitrary
JavaScript, sandboxed via Node's `vm` module, to customize deck.gl tooltips, click
behavior, and data transforms; the flag defaulted off and the feature saw negligible use.
Any saved charts with these fields set will simply ignore them going forward and fall back
to deck.gl's built-in field-based tooltips (`tooltip_contents`/`tooltip_template`) and
native click/cross-filter behavior. No migration is required; the fields are dropped
silently on next save.
### Owners, dashboard roles, and RLS roles replaced by Subjects
Superset now uses subject-based access assignments for dashboards, charts, datasets,

View File

@@ -405,12 +405,6 @@
"default": true,
"lifecycle": "deprecated",
"description": "Enable drill-to-detail functionality in charts"
},
{
"name": "ENABLE_JAVASCRIPT_CONTROLS",
"default": false,
"lifecycle": "deprecated",
"description": "Allow JavaScript in chart controls. WARNING: XSS security vulnerability!"
}
]
}

View File

@@ -114,7 +114,7 @@ export default {
},
fallback: {
tty: false,
vm: require.resolve('vm-browserify')
vm: false,
}
},
plugins: [...config.plugins, ...filteredPlugins],

View File

@@ -282,7 +282,6 @@
"tsx": "^4.23.0",
"typescript": "5.4.5",
"unzipper": "^0.12.5",
"vm-browserify": "^1.1.2",
"wait-on": "^9.0.10",
"webpack": "^5.108.4",
"webpack-bundle-analyzer": "^5.3.0",
@@ -8569,9 +8568,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -8589,9 +8585,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -8609,9 +8602,6 @@
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -8629,9 +8619,6 @@
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -8649,9 +8636,6 @@
"riscv64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -8669,9 +8653,6 @@
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -8689,9 +8670,6 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -8709,9 +8687,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -12646,13 +12621,6 @@
"node": ">=0.10.0"
}
},
"node_modules/@types/underscore": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.13.0.tgz",
"integrity": "sha512-L6LBgy1f0EFQZ+7uSA57+n2g/s4Qs5r06Vwrwn0/nuK1de+adz00NWaztRQ30aEqw5qOaWbPI8u2cGQ52lj6VA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/unist": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
@@ -15534,6 +15502,7 @@
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@@ -16512,6 +16481,7 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
@@ -16526,6 +16496,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
@@ -16805,6 +16776,7 @@
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT"
},
"node_modules/concat-stream": {
@@ -21394,6 +21366,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true,
"license": "ISC"
},
"node_modules/fsevents": {
@@ -22487,6 +22460,7 @@
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
@@ -23954,6 +23928,7 @@
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"dev": true,
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
@@ -24370,6 +24345,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -26888,6 +26864,21 @@
}
}
},
"node_modules/jsdom/node_modules/@noble/hashes": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz",
"integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/jsdom/node_modules/css-tree": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
@@ -30242,6 +30233,7 @@
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz",
"integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
@@ -30370,7 +30362,6 @@
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
@@ -31870,6 +31861,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
@@ -32794,6 +32786,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -36603,6 +36596,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -38561,6 +38555,7 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
@@ -38598,6 +38593,7 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/string.prototype.trim": {
@@ -38674,6 +38670,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@@ -40583,38 +40580,115 @@
}
},
"node_modules/typescript-json-schema": {
"version": "0.67.1",
"resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.67.1.tgz",
"integrity": "sha512-vKTZB/RoYTIBdVP7E7vrgHMCssBuhja91wQy498QIVhvfRimaOgjc98uwAXmZ7mbLUytJmOSbF11wPz+ByQeXg==",
"version": "0.68.0",
"resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.68.0.tgz",
"integrity": "sha512-1X60xXxkZpLAr125Jt5MT9VJTTxT0xfafbjdcbtml26nvpUr2p/P4V34JEXa+ObrHc71W9ksH5pemstY6y+SEw==",
"license": "BSD-3-Clause",
"dependencies": {
"@types/json-schema": "^7.0.9",
"@types/node": "^18.11.9",
"glob": "^7.1.7",
"@types/json-schema": "^7.0.15",
"@types/node": "^24.10.2",
"glob": "^13.0.6",
"path-equal": "^1.2.5",
"safe-stable-stringify": "^2.2.0",
"ts-node": "^10.9.1",
"typescript": "~5.5.0",
"vm2": "^3.10.0",
"yargs": "^17.1.1"
"safe-stable-stringify": "^2.5.0",
"ts-node": "^10.9.2",
"typescript": "~5.9.3",
"yargs": "^18.0.0"
},
"bin": {
"typescript-json-schema": "bin/typescript-json-schema"
}
},
"node_modules/typescript-json-schema/node_modules/@types/node": {
"version": "18.19.130",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
"version": "24.13.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
"integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
"undici-types": "~7.18.0"
}
},
"node_modules/typescript-json-schema/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/typescript-json-schema/node_modules/brace-expansion": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/typescript-json-schema/node_modules/glob": {
"version": "13.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"minimatch": "^10.2.2",
"minipass": "^7.1.3",
"path-scurry": "^2.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/typescript-json-schema/node_modules/lru-cache": {
"version": "11.5.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
"integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/typescript-json-schema/node_modules/minimatch": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.5"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/typescript-json-schema/node_modules/path-scurry": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/typescript-json-schema/node_modules/typescript": {
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -40625,29 +40699,11 @@
}
},
"node_modules/typescript-json-schema/node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"license": "MIT"
},
"node_modules/typescript-json-schema/node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/typewise": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz",
@@ -40724,6 +40780,7 @@
"version": "1.13.8",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz",
"integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==",
"dev": true,
"license": "MIT"
},
"node_modules/undici": {
@@ -41460,41 +41517,6 @@
"integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==",
"license": "MIT"
},
"node_modules/vm-browserify": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
"dev": true,
"license": "MIT"
},
"node_modules/vm2": {
"version": "3.11.5",
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.11.5.tgz",
"integrity": "sha512-RSrkBiwrj6FRU+QdqNs6KG0XdlvJCjpQ4GXiqmMbrhmwfu5k/XIMpAer0L8f6iuf0uJ3a4T1xJN126Q8yf0VIA==",
"license": "MIT",
"dependencies": {
"acorn": "^8.15.0",
"acorn-walk": "^8.3.4"
},
"bin": {
"vm2": "bin/vm2"
},
"engines": {
"node": ">=6.0"
}
},
"node_modules/vm2/node_modules/acorn": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
@@ -42410,6 +42432,21 @@
}
}
},
"node_modules/whatwg-url/node_modules/@noble/hashes": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz",
"integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/whatwg-url/node_modules/webidl-conversions": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
@@ -42603,6 +42640,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true,
"license": "ISC"
},
"node_modules/write-file-atomic": {
@@ -42860,6 +42898,7 @@
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
@@ -44349,14 +44388,12 @@
"prop-types": "^15.8.1",
"react-map-gl": "^8.1.1",
"tinycolor2": "^1.6.0",
"underscore": "^1.13.7",
"urijs": "^1.19.11",
"xss": "^1.0.15"
},
"devDependencies": {
"@types/mapbox__geojson-extent": "^1.0.3",
"@types/ngeohash": "^0.6.8",
"@types/underscore": "^1.13.0",
"@types/urijs": "^1.19.26"
},
"peerDependencies": {

View File

@@ -367,7 +367,6 @@
"tsx": "^4.23.0",
"typescript": "5.4.5",
"unzipper": "^0.12.5",
"vm-browserify": "^1.1.2",
"wait-on": "^9.0.10",
"webpack": "^5.108.4",
"webpack-bundle-analyzer": "^5.3.0",
@@ -425,6 +424,7 @@
"jest-util": "^30.4.0",
"jest-circus": "^30.4.0",
"jest-environment-node": "^30.4.0",
"typescript-json-schema": "^0.68.0",
"@babel/eslint-parser": {
"eslint": "$eslint"
},

View File

@@ -47,8 +47,6 @@ export enum FeatureFlag {
EnableAdvancedDataTypes = 'ENABLE_ADVANCED_DATA_TYPES',
EnableExtensions = 'ENABLE_EXTENSIONS',
EnableViewers = 'ENABLE_VIEWERS',
/** @deprecated */
EnableJavascriptControls = 'ENABLE_JAVASCRIPT_CONTROLS',
EnableTemplateProcessing = 'ENABLE_TEMPLATE_PROCESSING',
EscapeMarkdownHtml = 'ESCAPE_MARKDOWN_HTML',
EstimateQueryCost = 'ESTIMATE_QUERY_COST',

View File

@@ -51,7 +51,6 @@
"prop-types": "^15.8.1",
"react-map-gl": "^8.1.1",
"tinycolor2": "^1.6.0",
"underscore": "^1.13.7",
"urijs": "^1.19.11",
"xss": "^1.0.15",
"lodash-es": "^4.18.1"
@@ -59,7 +58,6 @@
"devDependencies": {
"@types/mapbox__geojson-extent": "^1.0.3",
"@types/ngeohash": "^0.6.8",
"@types/underscore": "^1.13.0",
"@types/urijs": "^1.19.26"
},
"peerDependencies": {

View File

@@ -40,7 +40,6 @@ import type { Layer } from '@deck.gl/core';
import Legend from './components/Legend';
import { hexToRGB } from './utils/colors';
import { getMapboxApiKey } from './utils/mapbox';
import sandboxedEval from './utils/sandbox';
import fitViewport, { Viewport } from './utils/fitViewport';
import {
DeckGLContainerHandle,
@@ -173,12 +172,6 @@ const CategoricalDeckGLContainer = (props: CategoricalDeckGLContainerProps) => {
// Add colors from categories or fixed color
features = addColor(features, fd, selectedColorScheme);
// Apply user defined data mutator if defined
if (fd.js_data_mutator) {
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
features = jsFnMutator(features);
}
// Show only categories selected in the legend
if (fd.dimension) {
features = features.filter(d => categories[d.cat_color]?.enabled);

View File

@@ -32,9 +32,6 @@ import {
filterNulls,
autozoom,
jsColumns,
jsDataMutator,
jsTooltip,
jsOnclickHref,
legendFormat,
legendPosition,
viewport,
@@ -168,12 +165,7 @@ const config: ControlPanelConfig = {
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
controlSetRows: [[jsColumns]],
},
],
controlOverrides: {

View File

@@ -127,9 +127,6 @@ export const ArcChartViz = ({
legend_position: 'tr',
legend_format: null,
js_columns: [],
js_data_mutator: '',
js_tooltip: '',
js_onclick_href: '',
}}
/>
);

View File

@@ -21,7 +21,6 @@ import { PolygonLayer } from '@deck.gl/layers';
import { Position } from '@deck.gl/core';
import { t } from '@apache-superset/core/translation';
import { commonLayerProps } from '../common';
import sandboxedEval from '../../utils/sandbox';
import { GetLayerType, createDeckGLComponent } from '../../factory';
import { ColorType } from '../../types';
import TooltipRow from '../../TooltipRow';
@@ -69,13 +68,8 @@ export const getLayer: GetLayerType<ContourLayer> = function ({
emitCrossFilters,
}) {
const fd = formData;
const {
aggregation = 'SUM',
js_data_mutator: jsFnMutator,
contours: rawContours,
cellSize = '200',
} = fd;
let data = payload.data.features;
const { aggregation = 'SUM', contours: rawContours, cellSize = '200' } = fd;
const data = payload.data.features;
// Store original data for tooltip access
const originalDataMap = new Map();
@@ -113,12 +107,6 @@ export const getLayer: GetLayerType<ContourLayer> = function ({
},
);
if (jsFnMutator) {
// Applying user defined data mutator if defined
const jsFnMutatorFunction = sandboxedEval(fd.js_data_mutator);
data = jsFnMutatorFunction(data);
}
// Create wrapper for tooltip content that adds nearby points
const tooltipContentGenerator = (o: any) => {
// Find nearby points based on hover coordinate

View File

@@ -26,9 +26,6 @@ import {
autozoom,
filterNulls,
jsColumns,
jsDataMutator,
jsOnclickHref,
jsTooltip,
mapboxStyle,
maplibreStyle,
mapProvider,
@@ -116,12 +113,7 @@ const config: ControlPanelConfig = {
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
controlSetRows: [[jsColumns]],
},
],
controlOverrides: {

View File

@@ -27,9 +27,7 @@ import { render } from '@testing-library/react';
import { SqlaFormData } from '@superset-ui/core';
import { supersetTheme, ThemeProvider } from '@apache-superset/core/theme';
import DeckGLGeoJson, {
computeGeoJsonTextOptionsFromJsOutput,
computeGeoJsonTextOptionsFromFormData,
computeGeoJsonIconOptionsFromJsOutput,
computeGeoJsonIconOptionsFromFormData,
getPoints,
getLayer,
@@ -61,26 +59,6 @@ jest.mock('react-map-gl/maplibre', () => ({
useControl: () => null,
}));
test('computeGeoJsonTextOptionsFromJsOutput returns an empty object for non-object input', () => {
expect(computeGeoJsonTextOptionsFromJsOutput(null)).toEqual({});
expect(computeGeoJsonTextOptionsFromJsOutput(42)).toEqual({});
expect(computeGeoJsonTextOptionsFromJsOutput([1, 2, 3])).toEqual({});
expect(computeGeoJsonTextOptionsFromJsOutput('string')).toEqual({});
});
test('computeGeoJsonTextOptionsFromJsOutput extracts valid text options from the input object', () => {
const input = {
getText: 'name',
getTextColor: [1, 2, 3, 255],
invalidOption: true,
};
const expectedOutput = {
getText: 'name',
getTextColor: [1, 2, 3, 255],
};
expect(computeGeoJsonTextOptionsFromJsOutput(input)).toEqual(expectedOutput);
});
test('computeGeoJsonTextOptionsFromFormData computes text options based on form data', () => {
const formData: SqlaFormData = {
label_property_name: 'name',
@@ -105,28 +83,6 @@ test('computeGeoJsonTextOptionsFromFormData computes text options based on form
expect(actualOutput.getText(sampleFeature)).toBe('Test');
});
test('computeGeoJsonIconOptionsFromJsOutput returns an empty object for non-object input', () => {
expect(computeGeoJsonIconOptionsFromJsOutput(null)).toEqual({});
expect(computeGeoJsonIconOptionsFromJsOutput(42)).toEqual({});
expect(computeGeoJsonIconOptionsFromJsOutput([1, 2, 3])).toEqual({});
expect(computeGeoJsonIconOptionsFromJsOutput('string')).toEqual({});
});
test('computeGeoJsonIconOptionsFromJsOutput extracts valid icon options from the input object', () => {
const input = {
getIcon: 'icon_name',
getIconColor: [1, 2, 3, 255],
invalidOption: false,
};
const expectedOutput = {
getIcon: 'icon_name',
getIconColor: [1, 2, 3, 255],
};
expect(computeGeoJsonIconOptionsFromJsOutput(input)).toEqual(expectedOutput);
});
test('computeGeoJsonIconOptionsFromFormData computes icon options based on form data', () => {
const formData: SqlaFormData = {
icon_url: 'https://example.com/icon.png',

View File

@@ -38,7 +38,6 @@ import {
DeckGLContainerStyledWrapper,
} from '../../DeckGLContainer';
import { hexToRGB } from '../../utils/colors';
import sandboxedEval from '../../utils/sandbox';
import { commonLayerProps } from '../common';
import TooltipRow from '../../TooltipRow';
import fitViewport, { Viewport } from '../../utils/fitViewport';
@@ -146,52 +145,6 @@ const getFillColor = (feature: JsonObject, filterStateValue: unknown[]) => {
};
const getLineColor = (feature: JsonObject) => feature?.properties?.strokeColor;
const isObject = (value: unknown): value is Record<string, unknown> =>
typeof value === 'object' && value !== null && !Array.isArray(value);
export const computeGeoJsonTextOptionsFromJsOutput = (
output: unknown,
): Partial<GeoJsonLayerProps> => {
if (!isObject(output)) return {};
// Properties sourced from:
// https://deck.gl/docs/api-reference/layers/geojson-layer#pointtype-options-2
const options: (keyof GeoJsonLayerProps)[] = [
'getText',
'getTextColor',
'getTextAngle',
'getTextSize',
'getTextAnchor',
'getTextAlignmentBaseline',
'getTextPixelOffset',
'getTextBackgroundColor',
'getTextBorderColor',
'getTextBorderWidth',
'textSizeUnits',
'textSizeScale',
'textSizeMinPixels',
'textSizeMaxPixels',
'textCharacterSet',
'textFontFamily',
'textFontWeight',
'textLineHeight',
'textMaxWidth',
'textWordBreak',
'textBackground',
'textBackgroundPadding',
'textOutlineColor',
'textOutlineWidth',
'textBillboard',
'textFontSettings',
];
const allEntries = Object.entries(output);
const validEntries = allEntries.filter(([k]) =>
options.includes(k as keyof GeoJsonLayerProps),
);
return Object.fromEntries(validEntries);
};
export const computeGeoJsonTextOptionsFromFormData = (
fd: SqlaFormData,
): Partial<GeoJsonLayerProps> => {
@@ -205,36 +158,6 @@ export const computeGeoJsonTextOptionsFromFormData = (
};
};
export const computeGeoJsonIconOptionsFromJsOutput = (
output: unknown,
): Partial<GeoJsonLayerProps> => {
if (!isObject(output)) return {};
// Properties sourced from:
// https://deck.gl/docs/api-reference/layers/geojson-layer#pointtype-options-1
const options: (keyof GeoJsonLayerProps)[] = [
'getIcon',
'getIconSize',
'getIconColor',
'getIconAngle',
'getIconPixelOffset',
'iconSizeUnits',
'iconSizeScale',
'iconSizeMinPixels',
'iconSizeMaxPixels',
'iconAtlas',
'iconMapping',
'iconBillboard',
'iconAlphaCutoff',
];
const allEntries = Object.entries(output);
const validEntries = allEntries.filter(([k]) =>
options.includes(k as keyof GeoJsonLayerProps),
);
return Object.fromEntries(validEntries);
};
export const computeGeoJsonIconOptionsFromFormData = (
fd: SqlaFormData,
): Partial<GeoJsonLayerProps> => ({
@@ -288,13 +211,6 @@ export const getLayer: GetLayerType<GeoJsonLayer> = function ({
features = [];
recurseGeoJson(payload.data, propOverrides);
let processedFeatures = features;
if (fd.js_data_mutator) {
// Applying user defined data mutator if defined
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
processedFeatures = jsFnMutator(features) as ProcessedFeature[];
}
let pointType = 'circle';
if (fd.enable_labels) {
pointType = `${pointType}+text`;
@@ -303,33 +219,17 @@ export const getLayer: GetLayerType<GeoJsonLayer> = function ({
pointType = `${pointType}+icon`;
}
let labelOpts: Partial<GeoJsonLayerProps> = {};
if (fd.enable_labels) {
if (fd.enable_label_javascript_mode) {
const generator = sandboxedEval(fd.label_javascript_config_generator);
if (typeof generator === 'function') {
labelOpts = computeGeoJsonTextOptionsFromJsOutput(generator());
}
} else {
labelOpts = computeGeoJsonTextOptionsFromFormData(fd);
}
}
const labelOpts: Partial<GeoJsonLayerProps> = fd.enable_labels
? computeGeoJsonTextOptionsFromFormData(fd)
: {};
let iconOpts: Partial<GeoJsonLayerProps> = {};
if (fd.enable_icons) {
if (fd.enable_icon_javascript_mode) {
const generator = sandboxedEval(fd.icon_javascript_config_generator);
if (typeof generator === 'function') {
iconOpts = computeGeoJsonIconOptionsFromJsOutput(generator());
}
} else {
iconOpts = computeGeoJsonIconOptionsFromFormData(fd);
}
}
const iconOpts: Partial<GeoJsonLayerProps> = fd.enable_icons
? computeGeoJsonIconOptionsFromFormData(fd)
: {};
return new GeoJsonLayer({
id: `geojson-layer-${fd.slice_id}` as const,
data: processedFeatures,
data: features,
extruded: fd.extruded,
filled: fd.filled,
stroked: fd.stroked,

View File

@@ -20,8 +20,6 @@ import { ControlPanelConfig } from '@superset-ui/chart-controls';
import { t } from '@apache-superset/core/translation';
import {
legacyValidateInteger,
isFeatureEnabled,
FeatureFlag,
validateNumber,
validateInteger,
} from '@superset-ui/core';
@@ -29,9 +27,6 @@ import { formatSelectOptions } from '../../utilities/utils';
import {
filterNulls,
jsColumns,
jsDataMutator,
jsTooltip,
jsOnclickHref,
fillColorPicker,
strokeColorPicker,
filled,
@@ -45,29 +40,11 @@ import {
lineWidth,
tooltipContents,
tooltipTemplate,
jsFunctionControl,
crossFilterColumn,
} from '../../utilities/Shared_DeckGL';
import { dndGeojsonColumn } from '../../utilities/sharedDndControls';
import { BLACK_COLOR } from '../../utilities/controls';
const defaultLabelConfigGenerator = `() => ({
// Check the documentation at:
// https://deck.gl/docs/api-reference/layers/geojson-layer#pointtype-options-2
getText: f => f.properties.name,
getTextColor: [0, 0, 0, 255],
getTextSize: 24,
textSizeUnits: 'pixels',
})`;
const defaultIconConfigGenerator = `() => ({
// Check the documentation at:
// https://deck.gl/docs/api-reference/layers/geojson-layer#pointtype-options-1
getIcon: () => ({ url: '', height: 128, width: 128 }),
getIconSize: 32,
iconSizeUnits: 'pixels',
})`;
const config: ControlPanelConfig = {
controlPanelSections: [
{
@@ -110,24 +87,6 @@ const config: ControlPanelConfig = {
},
},
],
[
{
name: 'enable_label_javascript_mode',
config: {
type: 'CheckboxControl',
label: t('Enable label JavaScript mode'),
description: t(
'Enables custom label configuration via JavaScript',
),
visibility: ({ form_data }) =>
!!form_data.enable_labels &&
isFeatureEnabled(FeatureFlag.EnableJavascriptControls),
default: false,
renderTrigger: true,
resetOnHide: false,
},
},
],
[
{
name: 'label_property_name',
@@ -135,10 +94,7 @@ const config: ControlPanelConfig = {
type: 'TextControl',
label: t('Label property name'),
description: t('The feature property to use for point labels'),
visibility: ({ form_data }) =>
!!form_data.enable_labels &&
(!form_data.enable_label_javascript_mode ||
!isFeatureEnabled(FeatureFlag.EnableJavascriptControls)),
visibility: ({ form_data }) => !!form_data.enable_labels,
default: 'name',
renderTrigger: true,
resetOnHide: false,
@@ -152,10 +108,7 @@ const config: ControlPanelConfig = {
type: 'ColorPickerControl',
label: t('Label color'),
description: t('The color of the point labels'),
visibility: ({ form_data }) =>
!!form_data.enable_labels &&
(!form_data.enable_label_javascript_mode ||
!isFeatureEnabled(FeatureFlag.EnableJavascriptControls)),
visibility: ({ form_data }) => !!form_data.enable_labels,
default: BLACK_COLOR,
renderTrigger: true,
resetOnHide: false,
@@ -170,10 +123,7 @@ const config: ControlPanelConfig = {
freeForm: true,
label: t('Label size'),
description: t('The font size of the point labels'),
visibility: ({ form_data }) =>
!!form_data.enable_labels &&
(!form_data.enable_label_javascript_mode ||
!isFeatureEnabled(FeatureFlag.EnableJavascriptControls)),
visibility: ({ form_data }) => !!form_data.enable_labels,
validators: [legacyValidateInteger],
choices: formatSelectOptions([8, 16, 24, 32, 64, 128]),
default: 24,
@@ -189,10 +139,7 @@ const config: ControlPanelConfig = {
type: 'SelectControl',
label: t('Label size unit'),
description: t('The unit for label size'),
visibility: ({ form_data }) =>
!!form_data.enable_labels &&
(!form_data.enable_label_javascript_mode ||
!isFeatureEnabled(FeatureFlag.EnableJavascriptControls)),
visibility: ({ form_data }) => !!form_data.enable_labels,
choices: [
['meters', t('Meters')],
['pixels', t('Pixels')],
@@ -203,27 +150,6 @@ const config: ControlPanelConfig = {
},
},
],
[
{
name: 'label_javascript_config_generator',
config: {
...jsFunctionControl(
t('Label JavaScript config generator'),
t(
'A JavaScript function that generates a label configuration object',
),
undefined,
undefined,
defaultLabelConfigGenerator,
),
visibility: ({ form_data }) =>
!!form_data.enable_labels &&
!!form_data.enable_label_javascript_mode &&
isFeatureEnabled(FeatureFlag.EnableJavascriptControls),
resetOnHide: false,
},
},
],
[
{
name: 'enable_icons',
@@ -236,24 +162,6 @@ const config: ControlPanelConfig = {
},
},
],
[
{
name: 'enable_icon_javascript_mode',
config: {
type: 'CheckboxControl',
label: t('Enable icon JavaScript mode'),
description: t(
'Enables custom icon configuration via JavaScript',
),
visibility: ({ form_data }) =>
!!form_data.enable_icons &&
isFeatureEnabled(FeatureFlag.EnableJavascriptControls),
default: false,
renderTrigger: true,
resetOnHide: false,
},
},
],
[
{
name: 'icon_url',
@@ -265,10 +173,7 @@ const config: ControlPanelConfig = {
'Note that the image URL must conform to the content ' +
'security policy (CSP) in order to load correctly.',
),
visibility: ({ form_data }) =>
!!form_data.enable_icons &&
(!form_data.enable_icon_javascript_mode ||
!isFeatureEnabled(FeatureFlag.EnableJavascriptControls)),
visibility: ({ form_data }) => !!form_data.enable_icons,
default: '',
renderTrigger: true,
resetOnHide: false,
@@ -283,10 +188,7 @@ const config: ControlPanelConfig = {
freeForm: true,
label: t('Icon size'),
description: t('The size of the point icons'),
visibility: ({ form_data }) =>
!!form_data.enable_icons &&
(!form_data.enable_icon_javascript_mode ||
!isFeatureEnabled(FeatureFlag.EnableJavascriptControls)),
visibility: ({ form_data }) => !!form_data.enable_icons,
validators: [legacyValidateInteger],
choices: formatSelectOptions([16, 24, 32, 64, 128]),
default: 32,
@@ -302,10 +204,7 @@ const config: ControlPanelConfig = {
type: 'SelectControl',
label: t('Icon size unit'),
description: t('The unit for icon size'),
visibility: ({ form_data }) =>
!!form_data.enable_icons &&
(!form_data.enable_icon_javascript_mode ||
!isFeatureEnabled(FeatureFlag.EnableJavascriptControls)),
visibility: ({ form_data }) => !!form_data.enable_icons,
choices: [
['meters', t('Meters')],
['pixels', t('Pixels')],
@@ -316,27 +215,6 @@ const config: ControlPanelConfig = {
},
},
],
[
{
name: 'icon_javascript_config_generator',
config: {
...jsFunctionControl(
t('Icon JavaScript config generator'),
t(
'A JavaScript function that generates an icon configuration object',
),
undefined,
undefined,
defaultIconConfigGenerator,
),
visibility: ({ form_data }) =>
!!form_data.enable_icons &&
!!form_data.enable_icon_javascript_mode &&
isFeatureEnabled(FeatureFlag.EnableJavascriptControls),
resetOnHide: false,
},
},
],
[lineWidth],
[
{
@@ -411,13 +289,7 @@ const config: ControlPanelConfig = {
},
{
label: t('Advanced'),
controlSetRows: [
[crossFilterColumn],
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
controlSetRows: [[crossFilterColumn], [jsColumns]],
},
],
};

View File

@@ -29,7 +29,6 @@ import {
getColorForBreakpoints,
getColorRange,
} from '../common';
import sandboxedEval from '../../utils/sandbox';
import { createDeckGLComponent, GetLayerType } from '../../factory';
import TooltipRow from '../../TooltipRow';
import { COLOR_SCHEME_TYPES } from '../../utilities/utils';
@@ -65,12 +64,7 @@ export const getLayer: GetLayerType<GridLayer> = function ({
const fd = formData;
const appliedScheme = fd.color_scheme;
const colorScale = CategoricalColorNamespace.getScale(appliedScheme);
let data = payload.data.features;
if (fd.js_data_mutator) {
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
data = jsFnMutator(data);
}
const data = payload.data.features;
const colorBreakpoints = fd.color_breakpoints;
@@ -132,13 +126,7 @@ export const getHighlightLayer: GetLayerType<GridLayer> = function ({
filterState,
}) {
const fd = formData;
let data = payload.data.features;
if (fd.js_data_mutator) {
// Applying user defined data mutator if defined
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
data = jsFnMutator(data);
}
const data = payload.data.features;
const aggFunc = getAggFunc(fd.js_agg_function, p => p.weight);

View File

@@ -26,9 +26,6 @@ import {
filterNulls,
autozoom,
jsColumns,
jsDataMutator,
jsTooltip,
jsOnclickHref,
extruded,
gridSize,
viewport,
@@ -77,12 +74,7 @@ const config: ControlPanelConfig = {
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
controlSetRows: [[jsColumns]],
},
],
controlOverrides: {

View File

@@ -93,9 +93,6 @@ export const GridChartViz = ({
grid_size: gridSize,
extruded,
js_columns: [],
js_data_mutator: '',
js_tooltip: '',
js_onclick_href: '',
}}
/>
);

View File

@@ -26,7 +26,6 @@ import {
} from '@superset-ui/core';
import { isPointInBonds } from '../../utilities/utils';
import { commonLayerProps, getColorRange } from '../common';
import sandboxedEval from '../../utils/sandbox';
import { GetLayerType, createDeckGLComponent } from '../../factory';
import TooltipRow from '../../TooltipRow';
import { createTooltipContent } from '../../utilities/tooltipUtils';
@@ -110,15 +109,9 @@ export const getLayer: GetLayerType<HeatmapLayer> = ({
intensity = 1,
radius_pixels: radiusPixels = 30,
aggregation = 'SUM',
js_data_mutator: jsFnMutator,
linear_color_scheme: colorScheme,
} = fd;
let data = payload.data.features;
if (jsFnMutator) {
const jsFnMutatorFunction = sandboxedEval(fd.js_data_mutator);
data = jsFnMutatorFunction(data);
}
const data = payload.data.features;
const colorScale = getSequentialSchemeRegistry()
?.get(colorScheme)
@@ -173,15 +166,8 @@ export const getHighlightLayer: GetLayerType<HeatmapLayer> = ({
intensity = 1,
radius_pixels: radiusPixels = 30,
aggregation = 'SUM',
js_data_mutator: jsFnMutator,
} = fd;
let data = payload.data.features;
if (jsFnMutator) {
// Applying user defined data mutator if defined
const jsFnMutatorFunction = sandboxedEval(fd.js_data_mutator);
data = jsFnMutatorFunction(data);
}
const data = payload.data.features;
const dataInside = data.filter((d: JsonObject) =>
isPointInBonds(d.position, filterState?.value),

View File

@@ -33,9 +33,6 @@ import {
deckGLLinearColorSchemeSelect,
filterNulls,
jsColumns,
jsDataMutator,
jsOnclickHref,
jsTooltip,
mapboxStyle,
maplibreStyle,
mapProvider,
@@ -150,12 +147,7 @@ const config: ControlPanelConfig = {
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
controlSetRows: [[jsColumns]],
},
],
controlOverrides: {

View File

@@ -30,7 +30,6 @@ import {
getColorForBreakpoints,
getColorRange,
} from '../common';
import sandboxedEval from '../../utils/sandbox';
import { GetLayerType, createDeckGLComponent } from '../../factory';
import {
createTooltipContent,
@@ -65,13 +64,7 @@ export const getLayer: GetLayerType<HexagonLayer> = function ({
const fd = formData;
const appliedScheme = fd.color_scheme;
const colorScale = CategoricalColorNamespace.getScale(appliedScheme);
let data = payload.data.features;
if (fd.js_data_mutator) {
// Applying user defined data mutator if defined
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
data = jsFnMutator(data);
}
const data = payload.data.features;
const colorSchemeType = fd.color_scheme_type;
const colorRange = getColorRange({
@@ -133,13 +126,7 @@ export const getHighlightLayer: GetLayerType<HexagonLayer> = function ({
filterState,
}) {
const fd = formData;
let data = payload.data.features;
if (fd.js_data_mutator) {
// Applying user defined data mutator if defined
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
data = jsFnMutator(data);
}
const data = payload.data.features;
const aggFunc = getAggFunc(fd.js_agg_function, p => p.weight);

View File

@@ -28,9 +28,6 @@ import {
generateDeckGLColorSchemeControls,
gridSize,
jsColumns,
jsDataMutator,
jsOnclickHref,
jsTooltip,
mapboxStyle,
maplibreStyle,
mapProvider,
@@ -103,12 +100,7 @@ const config: ControlPanelConfig = {
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
controlSetRows: [[jsColumns]],
},
],
formDataOverrides: formData => ({

View File

@@ -94,9 +94,6 @@ export const HexChartViz = ({
extruded,
js_agg_function: 'sum',
js_columns: [],
js_data_mutator: '',
js_tooltip: '',
js_onclick_href: '',
}}
/>
);

View File

@@ -20,7 +20,6 @@
import { PathLayer } from '@deck.gl/layers';
import { JsonObject, QueryFormData } from '@superset-ui/core';
import { commonLayerProps } from '../common';
import sandboxedEval from '../../utils/sandbox';
import { GetLayerType, createCategoricalDeckGLComponent } from '../../factory';
import { Point } from '../../types';
import {
@@ -115,11 +114,6 @@ export const getLayer: GetLayerType<PathLayer> = function ({
});
}
if (fd.js_data_mutator) {
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
data = jsFnMutator(data);
}
return new PathLayer({
id: `path-layer-${fd.slice_id}` as const,
getColor: (d: any) => d.color || [0, 0, 0, 255],
@@ -163,7 +157,7 @@ export const getHighlightLayer: GetLayerType<PathLayer> = function ({
const maxWidth = Number(fd.max_width) || 20;
const multiplier = Number(fd.line_width_multiplier) || 1;
const fixedColor = HIGHLIGHT_COLOR_ARRAY;
let data = payload.data.features.map((feature: JsonObject) => {
const data = payload.data.features.map((feature: JsonObject) => {
const baseWidth = Number.isFinite(feature.width) ? feature.width : 1;
let width = baseWidth * multiplier;
@@ -177,11 +171,6 @@ export const getHighlightLayer: GetLayerType<PathLayer> = function ({
};
});
if (fd.js_data_mutator) {
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
data = jsFnMutator(data);
}
const filteredData = data.filter(
(d: JsonObject) =>
JSON.stringify(d.path).replaceAll(' ', '') === filterState?.value?.[0],

View File

@@ -22,9 +22,6 @@ import {
filterNulls,
autozoom,
jsColumns,
jsDataMutator,
jsTooltip,
jsOnclickHref,
viewport,
lineType,
reverseLongLat,
@@ -155,12 +152,7 @@ const config: ControlPanelConfig = {
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
controlSetRows: [[jsColumns]],
},
],
};

View File

@@ -98,9 +98,6 @@ export const PathChartViz = ({
reverse_long_lat: false,
autozoom,
js_columns: ['color'],
js_data_mutator: '',
js_tooltip: '',
js_onclick_href: '',
}}
/>
);

View File

@@ -39,12 +39,6 @@ declare global {
}
}
export interface DeckPathTransformPropsFormData extends DeckPathFormData {
js_data_mutator?: string;
js_tooltip?: string;
js_onclick_href?: string;
}
interface PathFeature {
path: [number, number][];
metric?: number;
@@ -174,7 +168,7 @@ export default function transformProps(chartProps: ChartProps) {
reverse_long_lat = false,
js_columns,
breakpoint_metric,
} = formData as DeckPathTransformPropsFormData;
} = formData as DeckPathFormData;
// Check so legacy values still work
const fixedWidthValue =

View File

@@ -47,7 +47,6 @@ import {
} from '../../utils';
import { commonLayerProps, getColorForBreakpoints } from '../common';
import sandboxedEval from '../../utils/sandbox';
import getPointsFromPolygon from '../../utils/getPointsFromPolygon';
import fitViewport, { Viewport } from '../../utils/fitViewport';
import {
@@ -121,13 +120,7 @@ export const getLayer: GetLayerType<PolygonLayer> = function ({
const sc: { r: number; g: number; b: number; a: number } =
fd.stroke_color_picker;
const defaultBreakpointColor = fd.default_breakpoint_color;
let data = [...payload.data.features];
if (fd.js_data_mutator) {
// Applying user defined data mutator if defined
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
data = jsFnMutator(data);
}
const data = [...payload.data.features];
const colorSchemeType = fd.color_scheme_type;

View File

@@ -27,9 +27,6 @@ import {
filterNulls,
autozoom,
jsColumns,
jsDataMutator,
jsTooltip,
jsOnclickHref,
crossFilterColumn,
legendFormat,
legendPosition,
@@ -203,13 +200,7 @@ const config: ControlPanelConfig = {
},
{
label: t('Advanced'),
controlSetRows: [
[crossFilterColumn],
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
controlSetRows: [[crossFilterColumn], [jsColumns]],
},
],
controlOverrides: {

View File

@@ -138,9 +138,6 @@ export const PolygonChartViz = ({
legend_position: 'tr',
legend_format: null,
js_columns: ['population', 'area'],
js_data_mutator: '',
js_tooltip: '',
js_onclick_href: '',
}}
/>
);

View File

@@ -24,9 +24,6 @@ import {
filterNulls,
autozoom,
jsColumns,
jsDataMutator,
jsTooltip,
jsOnclickHref,
legendFormat,
legendPosition,
viewport,
@@ -146,12 +143,7 @@ const config: ControlPanelConfig = {
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
controlSetRows: [[jsColumns]],
},
],
controlOverrides: {

View File

@@ -107,9 +107,6 @@ export const ScatterChartViz = ({
color_scheme: 'bnbColors',
label_colors: {},
js_columns: [],
js_data_mutator: '',
js_tooltip: '',
js_onclick_href: '',
granularity: null,
}}
/>

View File

@@ -31,7 +31,6 @@ import {
ColorSchemeType,
isPointInBonds,
} from '../../utilities/utils';
import sandboxedEval from '../../utils/sandbox';
import { commonLayerProps, getColorRange } from '../common';
import TooltipRow from '../../TooltipRow';
import { GetLayerType, createDeckGLComponent } from '../../factory';
@@ -117,12 +116,7 @@ export const getLayer: GetLayerType<ScreenGridLayer> = function ({
const fd = formData;
const appliedScheme = fd.color_scheme;
const colorScale = CategoricalColorNamespace.getScale(appliedScheme);
let data = payload.data.features;
if (fd.js_data_mutator) {
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
data = jsFnMutator(data);
}
const data = payload.data.features;
const colorSchemeType = fd.color_scheme_type as ColorSchemeType & 'default';
const colorRange = getColorRange({
@@ -225,14 +219,8 @@ const getHighlightLayer: GetLayerType<ScreenGridLayer> = function ({
filterState,
payload,
}) {
const fd = formData;
let data = payload.data.features;
const data = payload.data.features;
if (fd.js_data_mutator) {
// Applying user defined data mutator if defined
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
data = jsFnMutator(data);
}
const dataInside = data.filter((d: JsonObject) =>
isPointInBonds(d.position, filterState?.value),
);

View File

@@ -27,9 +27,6 @@ import {
filterNulls,
autozoom,
jsColumns,
jsDataMutator,
jsTooltip,
jsOnclickHref,
gridSize,
viewport,
spatial,
@@ -96,12 +93,7 @@ const config: ControlPanelConfig = {
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
controlSetRows: [[jsColumns]],
},
],
controlOverrides: {

View File

@@ -85,9 +85,6 @@ export const ScreengridChartViz = ({
grid_size: gridSize,
color_picker: { a: 1, b: 0, g: 255, r: 14 },
js_columns: [],
js_data_mutator: '',
js_tooltip: '',
js_onclick_href: '',
}}
/>
);

View File

@@ -103,11 +103,8 @@ describe('commonLayerProps', () => {
) as any;
const mockOnSelect = jest.fn();
test('returns correct props when js_tooltip is provided', () => {
const formData = {
...partialformData,
js_tooltip: 'tooltip => tooltip.content',
} as QueryFormData;
test('returns correct props with a tooltip content generator', () => {
const formData = { ...partialformData } as QueryFormData;
const props = commonLayerProps({
formData,
setTooltip: mockSetTooltip,
@@ -118,7 +115,7 @@ describe('commonLayerProps', () => {
});
test('calls onHover and sets tooltip', () => {
const formData = { ...partialformData, js_tooltip: null } as QueryFormData;
const formData = { ...partialformData } as QueryFormData;
const props = commonLayerProps({
formData,
setTooltip: mockSetTooltip,

View File

@@ -40,7 +40,6 @@ import {
import { Layer, PickingInfo, Color } from '@deck.gl/core';
import { ScaleLinear } from 'd3-scale';
import { ColorBreakpointType } from '../types';
import sandboxedEval from '../utils/sandbox';
import { TooltipProps } from '../components/Tooltip';
import { getCrossFilterDataMask } from '../utils/crossFiltersDataMask';
import { COLOR_SCHEME_TYPES, ColorSchemeType } from '../utilities/utils';
@@ -68,11 +67,7 @@ export function commonLayerProps({
}) {
const fd = formData;
let onHover;
let tooltipContentGenerator = setTooltipContent;
if (fd.js_tooltip) {
tooltipContentGenerator = sandboxedEval(fd.js_tooltip);
}
if (tooltipContentGenerator) {
if (setTooltipContent) {
let currentTooltipContent: ReactNode = null;
const isCustomTooltip = (content: ReactNode): boolean =>
@@ -81,7 +76,7 @@ export function commonLayerProps({
onHover = (o: JsonObject) => {
if (o.picked) {
currentTooltipContent = tooltipContentGenerator(o);
currentTooltipContent = setTooltipContent(o);
}
if (
@@ -102,13 +97,7 @@ export function commonLayerProps({
}
let onClick;
if (fd.js_onclick_href) {
onClick = (o: any) => {
const href = sandboxedEval(fd.js_onclick_href)(o);
window.open(href);
return true;
};
} else if (fd.table_filter && onSelect !== undefined) {
if (fd.table_filter && onSelect !== undefined) {
onClick = (o: any) => {
onSelect(o.object[fd.line_column]);
return true;

View File

@@ -53,7 +53,6 @@ export interface SpatialFormData extends QueryFormData {
spatial: SpatialConfiguration;
size?: string;
grid_size?: number;
js_data_mutator?: string;
js_agg_function?: string;
js_columns?: string[];
color_scheme?: string;

View File

@@ -19,8 +19,6 @@
import { t } from '@apache-superset/core/translation';
import {
FeatureFlag,
isFeatureEnabled,
validateNonEmpty,
getCategoricalSchemeRegistry,
getSequentialSchemeRegistry,
@@ -151,49 +149,6 @@ const DEFAULT_VIEWPORT = {
pitch: 0,
};
const sandboxUrl =
'https://github.com/apache/superset/' +
'blob/master/superset-frontend/plugins/preset-chart-deckgl/src/utils/sandbox.ts';
const jsFunctionInfo = (
<div>
{t(
'For more information about objects are in context in the scope of this function, refer to the',
)}
<a href={sandboxUrl}>{t(" source code of Superset's sandboxed parser")}.</a>
.
</div>
);
export function jsFunctionControl(
label: string,
description: string,
extraDescr = null,
height = 100,
defaultText = '',
) {
return {
type: 'TextAreaControl',
language: 'javascript',
label,
description,
height,
default: defaultText,
aboveEditorSection: (
<div>
<p>{description}</p>
<p>{jsFunctionInfo}</p>
{extraDescr}
</div>
),
warning: !isFeatureEnabled(FeatureFlag.EnableJavascriptControls)
? t(
'This functionality is disabled in your environment for security reasons.',
)
: null,
readOnly: !isFeatureEnabled(FeatureFlag.EnableJavascriptControls),
};
}
export const filterNulls = {
name: 'filter_nulls',
config: {
@@ -255,36 +210,6 @@ export const crossFilterColumn: CustomControlItem = {
},
};
export const jsDataMutator = {
name: 'js_data_mutator',
config: jsFunctionControl(
t('JavaScript data interceptor'),
t(
'Define a javascript function that receives the data array used in the visualization ' +
'and is expected to return a modified version of that array. This can be used ' +
'to alter properties of the data, filter, or enrich the array.',
),
),
};
export const jsTooltip = {
name: 'js_tooltip',
config: jsFunctionControl(
t('JavaScript tooltip generator'),
t(
'Define a function that receives the input and outputs the content for a tooltip',
),
),
};
export const jsOnclickHref = {
name: 'js_onclick_href',
config: jsFunctionControl(
t('JavaScript onClick href'),
t('Define a function that returns a URL to navigate to when user clicks'),
),
};
export const legendFormat = {
name: 'legend_format',
config: {

View File

@@ -1,65 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// A safe alternative to JS's eval
import vm, { Context, RunningScriptOptions } from 'vm';
import _ from 'underscore';
/* eslint-disable-next-line no-restricted-syntax */
import * as d3array from 'd3-array';
/* eslint-disable-next-line no-restricted-syntax */
import * as colors from './colors';
// Objects exposed here should be treated like a public API
// if `underscore` had backwards incompatible changes in a future release, we'd
// have to be careful about bumping the library as those changes could break user charts
const GLOBAL_CONTEXT = {
console,
_,
colors,
d3array,
};
type GlobalContext = {
console: Console;
_: _.UnderscoreStatic;
colors: typeof colors;
d3array: typeof d3array;
};
// Copied/modified from https://github.com/hacksparrow/safe-eval/blob/master/index.js
export default function sandboxedEval(
code: string,
context?: Context,
opts?: RunningScriptOptions | string,
) {
const sandbox: Context = {};
const resultKey = `SAFE_EVAL_${Math.floor(Math.random() * 1000000)}`;
sandbox[resultKey] = {};
const codeToEval = `${resultKey}=${code}`;
const sandboxContext: GlobalContext = { ...GLOBAL_CONTEXT, ...context };
Object.keys(sandboxContext).forEach(key => {
sandbox[key] = sandboxContext[key as keyof GlobalContext];
});
try {
vm.runInNewContext(codeToEval, sandbox, opts);
return sandbox[resultKey];
} catch (error) {
return () => error;
}
}

View File

@@ -472,7 +472,7 @@ const config = {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.yml'],
fallback: {
fs: false,
vm: require.resolve('vm-browserify'),
vm: false,
path: false,
stream: require.resolve('stream-browserify'),
...(isDevMode ? { buffer: require.resolve('buffer/') } : {}), // Fix legacy-plugin-chart-paired-t-test broken Story

View File

@@ -936,9 +936,6 @@ DEFAULT_FEATURE_FLAGS: dict[str, bool] = {
# Enable drill-to-detail functionality in charts
# @lifecycle: deprecated
"DRILL_TO_DETAIL": True,
# Allow JavaScript in chart controls. WARNING: XSS security vulnerability!
# @lifecycle: deprecated
"ENABLE_JAVASCRIPT_CONTROLS": False,
}
# ------------------------------

View File

@@ -28,10 +28,6 @@ params:
datasource: 12__table
js_columns:
- color
js_data_mutator: "data => data.map(d => ({\n ...d,\n color: colors.hexToRGB(d.extraProps.color)\n\
}));"
js_onclick_href: ''
js_tooltip: ''
line_column: path_json
line_type: json
line_width: 150

View File

@@ -30,9 +30,6 @@ params:
filled: true
granularity_sqla: null
js_columns: []
js_data_mutator: ''
js_onclick_href: ''
js_tooltip: ''
legend_format: .1s
legend_position: tr
line_column: contour

View File

@@ -80,7 +80,6 @@ FRONTEND_CONF_KEYS = (
"SUPERSET_DASHBOARD_PERIODICAL_REFRESH_LIMIT",
"SUPERSET_DASHBOARD_PERIODICAL_REFRESH_WARNING_MESSAGE",
"SUPERSET_DASHBOARD_MANUAL_REFRESH_STAGGER_MS",
"ENABLE_JAVASCRIPT_CONTROLS",
"DEFAULT_SQLLAB_LIMIT",
"DEFAULT_VIZ_TYPE",
"SQL_MAX_ROW",

View File

@@ -46,7 +46,7 @@ from superset.exceptions import (
SupersetException,
SupersetSecurityException,
)
from superset.extensions import cache_manager, feature_flag_manager, security_manager
from superset.extensions import cache_manager, security_manager
from superset.legacy import update_time_range
from superset.models.core import Database
from superset.models.dashboard import Dashboard
@@ -65,23 +65,6 @@ from superset.viz import BaseViz
logger = logging.getLogger(__name__)
stats_logger = app.config["STATS_LOGGER"]
# Form-data keys whose values are executed as JavaScript at render time by the
# deck.gl charts (via the frontend ``sandboxedEval`` helper). These are stripped
# from incoming form_data unless the ``ENABLE_JAVASCRIPT_CONTROLS`` feature flag
# is enabled. Keep this list in sync with every ``sandboxedEval(fd.<key>)`` call
# site in the deck.gl plugins.
JS_CONTROL_FORM_DATA_KEYS: list[str] = [
"js_tooltip",
"js_onclick_href",
"js_data_mutator",
"label_javascript_config_generator",
"icon_javascript_config_generator",
]
REJECTED_FORM_DATA_KEYS: list[str] = []
if not feature_flag_manager.is_feature_enabled("ENABLE_JAVASCRIPT_CONTROLS"):
REJECTED_FORM_DATA_KEYS = list(JS_CONTROL_FORM_DATA_KEYS)
def redirect_to_login(next_target: str | None = None) -> FlaskResponse:
"""Return a redirect response to the login view, preserving target URL.
@@ -396,8 +379,6 @@ def get_form_data(
json_data = form_data["queries"][0] if "queries" in form_data else {}
form_data.update(json_data)
form_data = {k: v for k, v in form_data.items() if k not in REJECTED_FORM_DATA_KEYS}
# When a slice_id is present, load from DB and override
# the form_data from the DB with the other form_data provided
slice_id = form_data.get("slice_id") or slice_id

View File

@@ -235,9 +235,6 @@ class TestImportChartsCommand(SupersetTestCase):
"color_picker": {"a": 1, "b": 135, "g": 122, "r": 0},
"datasource": dataset.uid if dataset else None,
"js_columns": ["color"],
"js_data_mutator": "data => data.map(d => ({\\n ...d,\\n color: colors.hexToRGB(d.extraProps.color)\\n}));", # noqa: E501
"js_onclick_href": "",
"js_tooltip": "",
"line_column": "path_json",
"line_type": "json",
"line_width": 150,

View File

@@ -11,9 +11,6 @@
"js_columns": [
"color"
],
"js_datapoint_mutator": "d => {\n return {\n ...d,\n color: colors.hexToRGB(d.extraProps.color),\n }\n}",
"js_onclick_href": "",
"js_tooltip": "",
"mapbox_style": "mapbox://styles/mapbox/light-v9",
"reverse_long_lat": false,
"row_limit": 5000,

View File

@@ -11,9 +11,6 @@
"js_columns": [
"color"
],
"js_datapoint_mutator": "d => {\n return {\n ...d,\n color: colors.hexToRGB(d.extraProps.color),\n }\n}",
"js_onclick_href": "",
"js_tooltip": "",
"line_column": "path_json",
"line_type": "json",
"line_width": 150,

View File

@@ -588,9 +588,6 @@ chart_config: dict[str, Any] = {
"color_picker": {"a": 1, "b": 135, "g": 122, "r": 0},
"datasource": "12__table",
"js_columns": ["color"],
"js_data_mutator": r"data => data.map(d => ({\n ...d,\n color: colors.hexToRGB(d.extraProps.color)\n}));", # noqa: E501
"js_onclick_href": "",
"js_tooltip": "",
"line_column": "path_json",
"line_type": "json",
"line_width": 150,

View File

@@ -1,48 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Tests for superset.views.utils module"""
from superset.views.utils import (
get_form_data,
JS_CONTROL_FORM_DATA_KEYS,
REJECTED_FORM_DATA_KEYS,
)
def test_rejected_form_data_keys_cover_all_js_control_keys() -> None:
"""
With ENABLE_JAVASCRIPT_CONTROLS disabled (the default), every form_data key
that is later executed as JavaScript by the deck.gl charts must be rejected.
This guards against a new ``sandboxedEval(fd.<key>)`` call site being added
without also adding its key to the strip list.
"""
# The test app keeps ENABLE_JAVASCRIPT_CONTROLS at its default (off).
assert set(JS_CONTROL_FORM_DATA_KEYS) <= set(REJECTED_FORM_DATA_KEYS)
def test_get_form_data_strips_js_control_keys() -> None:
"""get_form_data drops all JS-executed keys when the flag is disabled."""
initial_form_data = dict.fromkeys(JS_CONTROL_FORM_DATA_KEYS, "data => data")
initial_form_data["viz_type"] = "deck_geojson"
form_data, _ = get_form_data(initial_form_data=initial_form_data)
for key in JS_CONTROL_FORM_DATA_KEYS:
assert key not in form_data
# Non-JS keys are preserved.
assert form_data["viz_type"] == "deck_geojson"