Compare commits

...

4 Commits

Author SHA1 Message Date
Evan
ff88f0dcdd test: fix deck.gl js_columns test to set the removed control explicitly
test_get_js_columns relied on the deck_path fixture's js_columns entry,
which was dropped when the "Extra data for JS" control was removed.
Set it explicitly in the test so the deprecated get_js_columns() method
is still exercised.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:34:15 -07:00
Evan Rusackas
b83e0a8400 chore(deckgl): remove js_columns (Extra data for JS) control
js_columns fed the now-removed JS eval controls and had no remaining
purpose: field-based tooltips already auto-include tooltip_contents
columns, and cross_filter_column is auto-included independently on the
Geojson/Polygon layers. Its only other effect was demoting matching
columns out of a feature's direct properties into extraProps, which
the crossFiltersDataMask extraProps fallback existed solely to
compensate for.

Removes the control from all 10 deck.gl layer control panels, dropping
the "Advanced" section entirely where it was the only control left
(Contour, Hex, Screengrid, Grid, Heatmap, Path, Scatter, Arc) and
keeping it on Geojson/Polygon for cross_filter_column. Also drops the
now-dead resolve_deck_gl_columns js_columns branch in the MCP chart
tool helpers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-17 15:48:42 -07:00
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
69 changed files with 241 additions and 1211 deletions

View File

@@ -24,6 +24,26 @@ 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.
The deck.gl "Extra data for JS" control (`js_columns`) has also been removed. It only
ever existed to feed extra columns into the JavaScript controls above; deck.gl's
built-in field-based tooltips and cross-filtering already pull in any columns they need
via `tooltip_contents`/`cross_filter_column`, so this control had no remaining purpose.
Any chart layer whose "Advanced" control panel section only contained this control no
longer has an "Advanced" section.
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,19 +32,12 @@ export interface DeckArcFormData extends SqlaFormData {
start_spatial: SpatialFormData['spatial'];
end_spatial: SpatialFormData['spatial'];
dimension?: string;
js_columns?: string[];
tooltip_contents?: unknown[];
tooltip_template?: string;
}
export default function buildQuery(formData: DeckArcFormData) {
const {
start_spatial,
end_spatial,
dimension,
js_columns,
tooltip_contents,
} = formData;
const { start_spatial, end_spatial, dimension, tooltip_contents } = formData;
if (!start_spatial || !end_spatial) {
throw new Error(
@@ -66,13 +59,6 @@ export default function buildQuery(formData: DeckArcFormData) {
columns = [...columns, dimension];
}
const jsColumns = ensureIsArray(js_columns || []);
jsColumns.forEach(col => {
if (!columns.includes(col)) {
columns.push(col);
}
});
columns = addTooltipColumnsToQuery(columns, tooltip_contents);
let filters = addSpatialNullFilters(

View File

@@ -31,10 +31,6 @@ import {
import {
filterNulls,
autozoom,
jsColumns,
jsDataMutator,
jsTooltip,
jsOnclickHref,
legendFormat,
legendPosition,
viewport,
@@ -166,15 +162,6 @@ const config: ControlPanelConfig = {
[legendFormat],
],
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
},
],
controlOverrides: {
size: {

View File

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

View File

@@ -17,11 +17,7 @@
* under the License.
*/
import { ChartProps } from '@superset-ui/core';
import {
processSpatialData,
addJsColumnsToExtraProps,
DataRecord,
} from '../spatialUtils';
import { processSpatialData, DataRecord } from '../spatialUtils';
import {
createBaseTransformResult,
getRecordsFromQuery,
@@ -43,7 +39,6 @@ function processArcData(
startSpatial: DeckArcFormData['start_spatial'],
endSpatial: DeckArcFormData['end_spatial'],
dimension?: string,
jsColumns?: string[],
): ArcPoint[] {
if (!startSpatial || !endSpatial || !records.length) {
return [];
@@ -52,9 +47,7 @@ function processArcData(
const startFeatures = processSpatialData(records, startSpatial);
const endFeatures = processSpatialData(records, endSpatial);
const excludeKeys = new Set(
['__timestamp', dimension, ...(jsColumns || [])].filter(
(key): key is string => key != null,
),
['__timestamp', dimension].filter((key): key is string => key != null),
);
return records
@@ -72,8 +65,6 @@ function processArcData(
extraProps: {},
};
arcPoint = addJsColumnsToExtraProps(arcPoint, record, jsColumns);
if (dimension && record[dimension] != null) {
arcPoint.cat_color = String(record[dimension]);
}
@@ -92,8 +83,7 @@ function processArcData(
export default function transformProps(chartProps: ChartProps) {
const { rawFormData: formData } = chartProps;
const { start_spatial, end_spatial, dimension, js_columns } =
formData as DeckArcFormData;
const { start_spatial, end_spatial, dimension } = formData as DeckArcFormData;
const records = getRecordsFromQuery(chartProps.queriesData);
const features = processArcData(
@@ -101,7 +91,6 @@ export default function transformProps(chartProps: ChartProps) {
start_spatial,
end_spatial,
dimension,
js_columns,
);
return createBaseTransformResult(chartProps, features);

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

@@ -25,10 +25,6 @@ import { validateNonEmpty } from '@superset-ui/core';
import {
autozoom,
filterNulls,
jsColumns,
jsDataMutator,
jsOnclickHref,
jsTooltip,
mapboxStyle,
maplibreStyle,
mapProvider,
@@ -114,15 +110,6 @@ const config: ControlPanelConfig = {
],
],
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
},
],
controlOverrides: {
size: {

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

@@ -24,15 +24,11 @@ import {
QueryObjectFilterClause,
SqlaFormData,
} from '@superset-ui/core';
import {
addJsColumnsToColumns,
addTooltipColumnsToQuery,
} from '../buildQueryUtils';
import { addTooltipColumnsToQuery } from '../buildQueryUtils';
export interface DeckGeoJsonFormData extends SqlaFormData {
geojson?: string;
filter_nulls?: boolean;
js_columns?: string[];
cross_filter_column?: string | null;
tooltip_contents?: unknown[];
}
@@ -41,7 +37,6 @@ export default function buildQuery(formData: DeckGeoJsonFormData) {
const {
geojson,
filter_nulls = true,
js_columns,
cross_filter_column,
tooltip_contents,
} = formData;
@@ -56,13 +51,6 @@ export default function buildQuery(formData: DeckGeoJsonFormData) {
geojson,
];
// Add js_columns
const columnStrings = columns.map(col =>
typeof col === 'string' ? col : col.label || col.sqlExpression || '',
);
const withJsColumns = addJsColumnsToColumns(columnStrings, js_columns);
columns = withJsColumns as QueryFormColumn[];
if (cross_filter_column && !columns.includes(cross_filter_column)) {
columns.push(cross_filter_column);
}

View File

@@ -20,18 +20,12 @@ import { ControlPanelConfig } from '@superset-ui/chart-controls';
import { t } from '@apache-superset/core/translation';
import {
legacyValidateInteger,
isFeatureEnabled,
FeatureFlag,
validateNumber,
validateInteger,
} from '@superset-ui/core';
import { formatSelectOptions } from '../../utilities/utils';
import {
filterNulls,
jsColumns,
jsDataMutator,
jsTooltip,
jsOnclickHref,
fillColorPicker,
strokeColorPicker,
filled,
@@ -45,29 +39,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 +86,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 +93,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 +107,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 +122,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 +138,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 +149,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 +161,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 +172,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 +187,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 +203,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 +214,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 +288,7 @@ const config: ControlPanelConfig = {
},
{
label: t('Advanced'),
controlSetRows: [
[crossFilterColumn],
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
controlSetRows: [[crossFilterColumn]],
},
],
};

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

@@ -25,10 +25,6 @@ import { validateNonEmpty } from '@superset-ui/core';
import {
filterNulls,
autozoom,
jsColumns,
jsDataMutator,
jsTooltip,
jsOnclickHref,
extruded,
gridSize,
viewport,
@@ -75,15 +71,6 @@ const config: ControlPanelConfig = {
[extruded],
],
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
},
],
controlOverrides: {
size: {

View File

@@ -92,10 +92,6 @@ export const GridChartViz = ({
autozoom,
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

@@ -32,10 +32,6 @@ import {
deckGLFixedColor,
deckGLLinearColorSchemeSelect,
filterNulls,
jsColumns,
jsDataMutator,
jsOnclickHref,
jsTooltip,
mapboxStyle,
maplibreStyle,
mapProvider,
@@ -148,15 +144,6 @@ const config: ControlPanelConfig = {
],
],
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
},
],
controlOverrides: {
size: {

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

@@ -27,10 +27,6 @@ import {
filterNulls,
generateDeckGLColorSchemeControls,
gridSize,
jsColumns,
jsDataMutator,
jsOnclickHref,
jsTooltip,
mapboxStyle,
maplibreStyle,
mapProvider,
@@ -101,15 +97,6 @@ const config: ControlPanelConfig = {
],
],
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
},
],
formDataOverrides: formData => ({
...formData,

View File

@@ -93,10 +93,6 @@ export const HexChartViz = ({
grid_size: gridSize,
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

@@ -339,7 +339,6 @@ test('Path buildQuery should handle line_width and breakpoint_metrics together t
value: 'SUM(distance)',
},
breakpoint_metric: 'AVG(speed)',
js_columns: ['color'],
tooltip_contents: ['name'],
row_limit: 500,
};
@@ -349,7 +348,6 @@ test('Path buildQuery should handle line_width and breakpoint_metrics together t
expect(query.metrics).toContain('SUM(distance)');
expect(query.metrics).toContain('AVG(speed)');
expect(query.columns).toContain('color');
expect(query.columns).toContain('name');
expect(query.row_limit).toBe(500);
});

View File

@@ -32,7 +32,6 @@ export interface DeckPathFormData extends SqlaFormData {
line_type?: 'polyline' | 'json' | 'geohash';
metric?: string;
reverse_long_lat?: boolean;
js_columns?: string[];
tooltip_contents?: unknown[];
tooltip_template?: string;
line_width?:
@@ -48,7 +47,6 @@ export default function buildQuery(formData: DeckPathFormData) {
const {
line_column,
metric,
js_columns,
tooltip_contents,
line_width,
dimension,
@@ -68,7 +66,6 @@ export default function buildQuery(formData: DeckPathFormData) {
const groupby = ensureIsArray(
baseQueryObject.groupby || [],
) as QueryFormColumn[];
const jsColumns = ensureIsArray(js_columns || []);
if (baseQueryObject.metrics?.length || metric) {
if (metric && !metrics.includes(metric)) {
@@ -124,12 +121,6 @@ export default function buildQuery(formData: DeckPathFormData) {
}
}
jsColumns.forEach(col => {
if (!columns.includes(col) && !groupby.includes(col)) {
columns.push(col);
}
});
const finalColumns = addTooltipColumnsToQuery(columns, tooltip_contents);
const finalGroupby = addTooltipColumnsToQuery(groupby, tooltip_contents);

View File

@@ -21,10 +21,6 @@ import { t } from '@apache-superset/core/translation';
import {
filterNulls,
autozoom,
jsColumns,
jsDataMutator,
jsTooltip,
jsOnclickHref,
viewport,
lineType,
reverseLongLat,
@@ -153,15 +149,6 @@ const config: ControlPanelConfig = {
}),
],
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
},
],
};

View File

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

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import { ChartProps, DTTM_ALIAS, getMetricLabel } from '@superset-ui/core';
import { addJsColumnsToExtraProps, DataRecord } from '../spatialUtils';
import { DataRecord } from '../spatialUtils';
import {
createBaseTransformResult,
getRecordsFromQuery,
@@ -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;
@@ -93,7 +87,6 @@ function processPathData(
lineType: 'polyline' | 'json' | 'geohash' = 'json',
reverseLongLat: boolean = false,
metricLabel?: string,
jsColumns?: string[],
widthMetricLabel?: string,
fixedWidthValue?: number | string | null,
categoryColumn?: string,
@@ -111,7 +104,6 @@ function processPathData(
metricLabel,
widthMetricLabel,
categoryColumn,
...(jsColumns || []),
].filter(Boolean) as string[],
);
@@ -157,7 +149,6 @@ function processPathData(
feature.cat_color = String(record[categoryColumn]);
}
feature = addJsColumnsToExtraProps(feature, record, jsColumns);
feature = addPropertiesToFeature(feature, record, excludeKeys);
return feature;
});
@@ -172,9 +163,8 @@ export default function transformProps(chartProps: ChartProps) {
line_width,
dimension,
reverse_long_lat = false,
js_columns,
breakpoint_metric,
} = formData as DeckPathTransformPropsFormData;
} = formData as DeckPathFormData;
// Check so legacy values still work
const fixedWidthValue =
@@ -207,7 +197,6 @@ export default function transformProps(chartProps: ChartProps) {
line_type,
reverse_long_lat,
metricLabel,
js_columns,
widthMetricLabel,
fixedWidthValue,
dimension,

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

@@ -318,22 +318,6 @@ describe('Polygon buildQuery', () => {
});
describe('Integration with other form data fields', () => {
test('should include js_columns in query columns', () => {
const formDataWithJsColumns = {
...baseFormData,
js_columns: ['custom_col1', 'custom_col2'],
};
const queryContext = buildQuery(formDataWithJsColumns);
const [query] = queryContext.queries;
expect(query.columns).toEqual([
'polygon_geom',
'custom_col1',
'custom_col2',
]);
});
test('should include tooltip_contents columns in query', () => {
const formDataWithTooltips = {
...baseFormData,
@@ -375,20 +359,19 @@ describe('Polygon buildQuery', () => {
expect(query.columns).toContain('sa3_name');
});
test('should not duplicate cross_filter_column when it overlaps with another column', () => {
test('should not duplicate cross_filter_column when it overlaps with line_column', () => {
const formDataWithOverlap = {
...baseFormData,
js_columns: ['sa3_name', 'extra_col'],
cross_filter_column: 'sa3_name',
cross_filter_column: 'polygon_geom',
};
const queryContext = buildQuery(formDataWithOverlap);
const [query] = queryContext.queries;
const sa3Count = query.columns?.filter(
(col: unknown) => col === 'sa3_name',
const geomCount = query.columns?.filter(
(col: unknown) => col === 'polygon_geom',
).length;
expect(sa3Count).toBe(1);
expect(geomCount).toBe(1);
});
test('should not add cross_filter_column to query columns when unset', () => {
@@ -412,7 +395,6 @@ describe('Polygon buildQuery', () => {
label: 'AVG(elevation)',
},
},
js_columns: ['custom_prop'],
tooltip_contents: [
{ item_type: 'column', column_name: 'tooltip_info' },
],
@@ -423,7 +405,6 @@ describe('Polygon buildQuery', () => {
const [query] = queryContext.queries;
expect(query.columns).toContain('polygon_geom');
expect(query.columns).toContain('custom_prop');
expect(query.columns).toContain('tooltip_info');
expect(query.metrics).toContain('population');
expect(query.metrics).toContain(complexFormData.point_radius_fixed.value);

View File

@@ -46,7 +46,6 @@ export interface DeckPolygonFormData extends SqlaFormData {
};
reverse_long_lat?: boolean;
filter_nulls?: boolean;
js_columns?: string[];
cross_filter_column?: string | null;
tooltip_contents?: unknown[];
tooltip_template?: string;
@@ -58,7 +57,6 @@ export default function buildQuery(formData: DeckPolygonFormData) {
metric,
point_radius_fixed,
filter_nulls = true,
js_columns,
cross_filter_column,
tooltip_contents,
} = formData;
@@ -73,13 +71,6 @@ export default function buildQuery(formData: DeckPolygonFormData) {
line_column,
];
const jsColumns = ensureIsArray(js_columns || []);
jsColumns.forEach((col: string) => {
if (!columns.includes(col)) {
columns.push(col);
}
});
if (cross_filter_column && !columns.includes(cross_filter_column)) {
columns.push(cross_filter_column);
}

View File

@@ -26,10 +26,6 @@ import { COLOR_SCHEME_TYPES, formatSelectOptions } from '../../utilities/utils';
import {
filterNulls,
autozoom,
jsColumns,
jsDataMutator,
jsTooltip,
jsOnclickHref,
crossFilterColumn,
legendFormat,
legendPosition,
@@ -203,13 +199,7 @@ const config: ControlPanelConfig = {
},
{
label: t('Advanced'),
controlSetRows: [
[crossFilterColumn],
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
controlSetRows: [[crossFilterColumn]],
},
],
controlOverrides: {

View File

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

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import { ChartProps, getMetricLabel, QueryFormMetric } from '@superset-ui/core';
import { addJsColumnsToExtraProps, DataRecord } from '../spatialUtils';
import { DataRecord } from '../spatialUtils';
import {
createBaseTransformResult,
getRecordsFromQuery,
@@ -104,7 +104,6 @@ function processPolygonData(
metric,
point_radius_fixed,
reverse_long_lat,
js_columns,
} = formData;
if (!line_column || !records.length) {
@@ -139,7 +138,7 @@ function processPolygonData(
}
}
const excludeKeys = new Set([line_column, ...(js_columns || [])]);
const excludeKeys = new Set([line_column]);
return records.flatMap(record => {
let feature: PolygonFeature = {
@@ -147,7 +146,6 @@ function processPolygonData(
metrics: {},
};
feature = addJsColumnsToExtraProps(feature, record, js_columns);
const updatedFeature = addPropertiesToFeature(
feature as unknown as Record<string, unknown>,
record,

View File

@@ -195,23 +195,6 @@ test('Scatter buildQuery should handle tooltip_contents', () => {
expect(query.columns).toContain('description');
});
test('Scatter buildQuery should handle js_columns', () => {
const formData: DeckScatterFormData = {
...baseFormData,
js_columns: ['custom_col1', 'custom_col2'],
point_radius_fixed: {
type: 'fix',
value: '1000',
},
};
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.columns).toContain('custom_col1');
expect(query.columns).toContain('custom_col2');
});
test('Scatter buildQuery should handle adhoc SQL metric for point_radius_fixed', () => {
const adhocMetric = {
label: 'count(*) * 1.1',

View File

@@ -23,7 +23,6 @@ import {
QueryFormMetric,
QueryFormOrderBy,
SqlaFormData,
QueryFormColumn,
QueryObject,
} from '@superset-ui/core';
import {
@@ -31,10 +30,7 @@ import {
addSpatialNullFilters,
SpatialFormData,
} from '../spatialUtils';
import {
addJsColumnsToColumns,
addTooltipColumnsToQuery,
} from '../buildQueryUtils';
import { addTooltipColumnsToQuery } from '../buildQueryUtils';
import { isMetricValue } from '../utils/metricUtils';
export interface DeckScatterFormData
@@ -55,13 +51,7 @@ export interface DeckScatterFormData
}
export default function buildQuery(formData: DeckScatterFormData) {
const {
spatial,
point_radius_fixed,
dimension,
js_columns,
tooltip_contents,
} = formData;
const { spatial, point_radius_fixed, dimension, tooltip_contents } = formData;
if (!spatial) {
throw new Error('Spatial configuration is required for Scatter charts');
@@ -76,12 +66,6 @@ export default function buildQuery(formData: DeckScatterFormData) {
columns.push(dimension);
}
const columnStrings = columns.map(col =>
typeof col === 'string' ? col : col.label || col.sqlExpression || '',
);
const withJsColumns = addJsColumnsToColumns(columnStrings, js_columns);
columns = withJsColumns as QueryFormColumn[];
columns = addTooltipColumnsToQuery(columns, tooltip_contents);
// Only add metric if point_radius_fixed is a metric type

View File

@@ -23,10 +23,6 @@ import timeGrainSqlaAnimationOverrides from '../../utilities/controls';
import {
filterNulls,
autozoom,
jsColumns,
jsDataMutator,
jsTooltip,
jsOnclickHref,
legendFormat,
legendPosition,
viewport,
@@ -144,15 +140,6 @@ const config: ControlPanelConfig = {
...generateDeckGLColorSchemeControls({}),
],
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
},
],
controlOverrides: {
size: {

View File

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

View File

@@ -43,7 +43,6 @@ function processScatterData(
spatial: DeckScatterFormData['spatial'],
radiusMetricLabel?: string,
categoryColumn?: string,
jsColumns?: string[],
fixedRadiusValue?: number | string | null,
): ScatterPoint[] {
if (!spatial || !records.length) {
@@ -65,7 +64,6 @@ function processScatterData(
: []),
radiusMetricLabel,
categoryColumn,
...(jsColumns || []),
]);
return spatialFeatures.map(feature => {
@@ -105,7 +103,7 @@ function processScatterData(
export default function transformProps(chartProps: ChartProps) {
const { rawFormData: formData } = chartProps;
const { spatial, point_radius_fixed, dimension, js_columns } =
const { spatial, point_radius_fixed, dimension } =
formData as DeckScatterFormData;
// Check if this is a fixed value or metric
@@ -121,7 +119,6 @@ export default function transformProps(chartProps: ChartProps) {
spatial,
radiusMetricLabel,
dimension,
js_columns,
fixedRadiusValue,
);

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

@@ -26,10 +26,6 @@ import timeGrainSqlaAnimationOverrides from '../../utilities/controls';
import {
filterNulls,
autozoom,
jsColumns,
jsDataMutator,
jsTooltip,
jsOnclickHref,
gridSize,
viewport,
spatial,
@@ -94,15 +90,6 @@ const config: ControlPanelConfig = {
[deckGLCategoricalColorSchemeSelect],
],
},
{
label: t('Advanced'),
controlSetRows: [
[jsColumns],
[jsDataMutator],
[jsTooltip],
[jsOnclickHref],
],
},
],
controlOverrides: {
size: {

View File

@@ -84,10 +84,6 @@ export const ScreengridChartViz = ({
autozoom,
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

@@ -23,26 +23,6 @@ import {
getColumnLabel,
} from '@superset-ui/core';
export function addJsColumnsToColumns(
columns: string[],
jsColumns?: string[],
existingColumns?: string[],
): string[] {
if (!jsColumns?.length) return columns;
const allExisting = new Set([...columns, ...(existingColumns || [])]);
const result = [...columns];
jsColumns.forEach(col => {
if (!allExisting.has(col)) {
result.push(col);
allExisting.add(col);
}
});
return result;
}
export function addNullFilters(
filters: QueryObjectFilterClause[],
columnNames: string[],

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

@@ -199,7 +199,6 @@ describe('spatialUtils', () => {
latCol: 'latitude',
},
size: 'count',
js_columns: ['extra_col'],
} as SpatialFormData;
buildSpatialQuery(formData);
@@ -220,20 +219,21 @@ describe('spatialUtils', () => {
latCol: 'latitude',
};
const metricLabel = 'count';
const jsColumns = ['extra'];
const result = processSpatialData(records, spatial, metricLabel, jsColumns);
const result = processSpatialData(records, spatial, metricLabel);
expect(result).toHaveLength(2);
expect(result[0]).toEqual({
position: [-122.4, 37.8],
weight: 10,
extraProps: { extra: 'test1' },
extraProps: {},
extra: 'test1',
});
expect(result[1]).toEqual({
position: [-122.5, 37.9],
weight: 20,
extraProps: { extra: 'test2' },
extraProps: {},
extra: 'test2',
});
});
@@ -398,22 +398,21 @@ describe('spatialUtils', () => {
lonCol: 'longitude',
latCol: 'latitude',
};
const jsColumns = ['extra_col'];
const result = processSpatialData(records, spatial, 'count', jsColumns);
const result = processSpatialData(records, spatial, 'count');
expect(result[0]).toEqual({
position: [-122.4, 37.8],
weight: 10,
extraProps: { extra_col: 'extra_value' },
extraProps: {},
category: 'A',
description: 'Test location',
extra_col: 'extra_value',
});
expect(result[0]).not.toHaveProperty('longitude');
expect(result[0]).not.toHaveProperty('latitude');
expect(result[0]).not.toHaveProperty('count');
expect(result[0]).not.toHaveProperty('extra_col');
});
test('transformSpatialProps transforms chart props correctly', () => {
@@ -452,7 +451,6 @@ describe('spatialUtils', () => {
latCol: 'latitude',
},
size: 'count',
js_columns: [],
viewport: {
zoom: 10,
latitude: 37.8,
@@ -471,7 +469,6 @@ describe('spatialUtils', () => {
latCol: 'latitude',
},
size: 'count',
js_columns: [],
viewport: {
zoom: 10,
latitude: 37.8,

View File

@@ -53,9 +53,7 @@ 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;
color_scheme_type?: string;
color_breakpoints?: number[];
@@ -119,7 +117,7 @@ export function addSpatialNullFilters(
}
export function buildSpatialQuery(formData: SpatialFormData) {
const { spatial, size: metric, js_columns, tooltip_contents } = formData;
const { spatial, size: metric, tooltip_contents } = formData;
if (!spatial) {
throw new Error(`Spatial configuration is required for this chart`);
@@ -130,14 +128,6 @@ export function buildSpatialQuery(formData: SpatialFormData) {
let columns = [...(baseQueryObject.columns || []), ...spatialColumns];
const metrics = metric ? [metric] : [];
if (js_columns?.length) {
js_columns.forEach(col => {
if (!columns.includes(col)) {
columns.push(col);
}
});
}
columns = addTooltipColumnsToQuery(columns, tooltip_contents);
const filters = addSpatialNullFilters(
@@ -207,29 +197,10 @@ function reverseGeohashDecode(geohashCode: string): [number, number] | null {
}
}
export function addJsColumnsToExtraProps<
T extends { extraProps?: Record<string, unknown> },
>(feature: T, record: DataRecord, jsColumns?: string[]): T {
if (!jsColumns?.length) {
return feature;
}
const extraProps: Record<string, unknown> = { ...feature.extraProps };
jsColumns.forEach(col => {
if (record[col] !== undefined) {
extraProps[col] = record[col];
}
});
return { ...feature, extraProps };
}
export function processSpatialData(
records: DataRecord[],
spatial: SpatialConfiguration,
metricLabel?: string,
jsColumns?: string[],
): SpatialPoint[] {
if (!spatial || !records.length) {
return [];
@@ -237,7 +208,6 @@ export function processSpatialData(
const features: SpatialPoint[] = [];
const spatialColumns = getSpatialColumns(spatial);
const jsColumnsSet = jsColumns ? new Set(jsColumns) : null;
const spatialColumnsSet = new Set(spatialColumns);
for (const record of records) {
@@ -286,13 +256,12 @@ export function processSpatialData(
}
}
let spatialPoint: SpatialPoint = {
const spatialPoint: SpatialPoint = {
position,
weight,
extraProps: {},
};
spatialPoint = addJsColumnsToExtraProps(spatialPoint, record, jsColumns);
Object.keys(record).forEach(key => {
if (spatialColumnsSet.has(key)) {
return;
@@ -302,10 +271,6 @@ export function processSpatialData(
return;
}
if (jsColumnsSet?.has(key)) {
return;
}
spatialPoint[key] = record[key];
});
@@ -345,17 +310,12 @@ export function transformSpatialProps(chartProps: ChartProps) {
setDataMask = NOOP,
} = hooks;
const { spatial, size: metric, js_columns } = formData as SpatialFormData;
const { spatial, size: metric } = formData as SpatialFormData;
const metricLabel = metric ? getMetricLabel(metric) : undefined;
const queryData = queriesData[0];
const records = queryData?.data || [];
const features = processSpatialData(
records,
spatial,
metricLabel,
js_columns,
);
const features = processSpatialData(records, spatial, metricLabel);
return {
datasource,

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: {
@@ -228,18 +183,6 @@ export const dimension: CustomControlItem = {
},
};
export const jsColumns = {
name: 'js_columns',
config: {
...sharedControls.groupby,
label: t('Extra data for JS'),
default: [],
description: t(
'List of extra columns made available in JavaScript functions',
),
},
};
export const crossFilterColumn: CustomControlItem = {
name: 'cross_filter_column',
config: {
@@ -255,36 +198,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

@@ -447,52 +447,6 @@ describe('getCrossFilterDataMask', () => {
});
});
test('deck_polygon with cross_filter_column reads from extraProps fallback', () => {
const polygonFormData = {
...formData,
line_column: 'geojson',
cross_filter_column: 'region_id',
};
const polygonPickingData = {
...pickingData,
object: {
polygon: [
[-122.42, 37.8],
[-122.42, 37.81],
[-122.41, 37.81],
[-122.41, 37.8],
[-122.42, 37.8],
],
extraProps: { region_id: 42 },
},
};
const dataMask = getCrossFilterDataMask({
formData: polygonFormData,
data: polygonPickingData,
filterState: {},
});
expect(dataMask).toStrictEqual({
dataMask: {
extraFormData: {
filters: [
{
col: 'region_id',
op: '==',
val: 42,
},
],
},
filterState: {
value: [42],
},
},
isCurrentValueSelected: false,
});
});
test('deck_polygon throws when cross_filter_column value missing on feature', () => {
const polygonFormData = {
...formData,

View File

@@ -414,14 +414,12 @@ const getLineColumnFilters = ({
if (!path) throw new Error('Position of picked data is required');
// Preferred path: emit on a dimension column the user selected. The value
// can land either directly on the picked feature (groupby/excluded keys are
// spread by addPropertiesToFeature) or under extraProps when it overlaps
// with js_columns (addJsColumnsToExtraProps).
// lands directly on the picked feature (groupby/excluded keys are spread
// by addPropertiesToFeature).
if (formData.cross_filter_column) {
const col = formData.cross_filter_column;
const obj = data.object ?? {};
const extraProps = (obj.extraProps ?? {}) as Record<string, unknown>;
const dimensionVal = (obj[col] ?? extraProps[col]) as
const dimensionVal = obj[col] as
string | number | boolean | null | undefined;
if (dimensionVal == null) {

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

@@ -26,12 +26,6 @@ params:
g: 122
r: 0
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

@@ -29,10 +29,6 @@ params:
r: 3
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

@@ -386,10 +386,6 @@ def resolve_deck_gl_columns(form_data: dict[str, Any]) -> list[str]:
for field in ("line_column", "geojson", "dimension"):
_add(form_data.get(field))
for col in form_data.get("js_columns") or []:
if isinstance(col, str):
_add(col)
return columns

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

@@ -234,10 +234,6 @@ class TestImportChartsCommand(SupersetTestCase):
"annotation_layers": [],
"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

@@ -8,12 +8,6 @@
"datasource": "12__table",
"filters": [],
"having": "",
"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

@@ -8,12 +8,6 @@
"datasource": "12__table",
"filters": [],
"having": "",
"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

@@ -587,10 +587,6 @@ chart_config: dict[str, Any] = {
"params": {
"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

@@ -914,7 +914,10 @@ class TestBaseDeckGLViz(SupersetTestCase):
assert result == []
def test_get_js_columns(self):
form_data = load_fixture("deck_path_form_data.json")
form_data = {
**load_fixture("deck_path_form_data.json"),
"js_columns": ["color"],
}
datasource = self.get_datasource_mock()
mock_d = {"a": "dummy1", "b": "dummy2", "c": "dummy3"}
test_viz_deckgl = viz.BaseDeckGLViz(datasource, form_data)

View File

@@ -344,18 +344,15 @@ def test_resolve_deck_gl_columns_geojson():
assert resolve_deck_gl_columns(form_data) == ["geom_col"]
def test_resolve_deck_gl_columns_with_dimension_and_js_columns():
def test_resolve_deck_gl_columns_with_dimension():
form_data = {
"spatial": {"type": "latlong", "lonCol": "lon", "latCol": "lat"},
"dimension": "category",
"js_columns": ["name", "value"],
}
cols = resolve_deck_gl_columns(form_data)
assert "lon" in cols
assert "lat" in cols
assert "category" in cols
assert "name" in cols
assert "value" in cols
def test_resolve_deck_gl_columns_deduplicates():
@@ -371,13 +368,6 @@ def test_resolve_deck_gl_columns_empty():
assert resolve_deck_gl_columns({}) == []
def test_resolve_deck_gl_columns_ignores_non_string_js_columns():
form_data = {
"js_columns": [42, None, "valid_col"],
}
assert resolve_deck_gl_columns(form_data) == ["valid_col"]
# ---------------------------------------------------------------------------
# build_query_dicts_from_form_data — Deck.gl branch
# ---------------------------------------------------------------------------

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"