mirror of
https://github.com/apache/superset.git
synced 2026-07-18 04:35:40 +00:00
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>
212 lines
5.8 KiB
TypeScript
212 lines
5.8 KiB
TypeScript
/**
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
import { ChartProps, DTTM_ALIAS, getMetricLabel } from '@superset-ui/core';
|
|
import { addJsColumnsToExtraProps, DataRecord } from '../spatialUtils';
|
|
import {
|
|
createBaseTransformResult,
|
|
getRecordsFromQuery,
|
|
getMetricLabelFromFormData,
|
|
parseMetricValue,
|
|
addPropertiesToFeature,
|
|
} from '../transformUtils';
|
|
import { DeckPathFormData } from './buildQuery';
|
|
import { isFixedValue, getFixedValue } from '../utils/metricUtils';
|
|
|
|
declare global {
|
|
interface Window {
|
|
polyline?: {
|
|
decode: (data: string) => [number, number][];
|
|
};
|
|
geohash?: {
|
|
decode: (data: string) => { longitude: number; latitude: number };
|
|
};
|
|
}
|
|
}
|
|
|
|
interface PathFeature {
|
|
path: [number, number][];
|
|
metric?: number;
|
|
timestamp?: unknown;
|
|
width?: number;
|
|
cat_color?: string;
|
|
extraProps?: Record<string, unknown>;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
const decoders = {
|
|
json: (data: string): [number, number][] => {
|
|
try {
|
|
const parsed = JSON.parse(data);
|
|
return Array.isArray(parsed) ? parsed : [];
|
|
} catch (error) {
|
|
return [];
|
|
}
|
|
},
|
|
polyline: (data: string): [number, number][] => {
|
|
try {
|
|
if (typeof window !== 'undefined' && window.polyline) {
|
|
return window.polyline.decode(data);
|
|
}
|
|
return [];
|
|
} catch (error) {
|
|
return [];
|
|
}
|
|
},
|
|
geohash: (data: string): [number, number][] => {
|
|
try {
|
|
if (typeof window !== 'undefined' && window.geohash) {
|
|
const decoded = window.geohash.decode(data);
|
|
return [[decoded.longitude, decoded.latitude]];
|
|
}
|
|
return [];
|
|
} catch (error) {
|
|
return [];
|
|
}
|
|
},
|
|
};
|
|
|
|
function processPathData(
|
|
records: DataRecord[],
|
|
lineColumn: string,
|
|
lineType: 'polyline' | 'json' | 'geohash' = 'json',
|
|
reverseLongLat: boolean = false,
|
|
metricLabel?: string,
|
|
jsColumns?: string[],
|
|
widthMetricLabel?: string,
|
|
fixedWidthValue?: number | string | null,
|
|
categoryColumn?: string,
|
|
): PathFeature[] {
|
|
if (!records.length || !lineColumn) {
|
|
return [];
|
|
}
|
|
|
|
const decoder = decoders[lineType] || decoders.json;
|
|
const excludeKeys = new Set(
|
|
[
|
|
lineType !== 'geohash' ? lineColumn : undefined,
|
|
'timestamp',
|
|
DTTM_ALIAS,
|
|
metricLabel,
|
|
widthMetricLabel,
|
|
categoryColumn,
|
|
...(jsColumns || []),
|
|
].filter(Boolean) as string[],
|
|
);
|
|
|
|
return records.map(record => {
|
|
const lineData = record[lineColumn];
|
|
let path: [number, number][] = [];
|
|
|
|
if (lineData) {
|
|
path = decoder(String(lineData));
|
|
if (reverseLongLat && path.length > 0) {
|
|
path = path.map(([lng, lat]) => [lat, lng]);
|
|
}
|
|
}
|
|
|
|
let feature: PathFeature = {
|
|
path,
|
|
timestamp: record[DTTM_ALIAS],
|
|
extraProps: {},
|
|
};
|
|
|
|
if (metricLabel && record[metricLabel] != null) {
|
|
const metricValue = parseMetricValue(record[metricLabel]);
|
|
if (metricValue !== undefined) {
|
|
feature.metric = metricValue;
|
|
}
|
|
}
|
|
// Set width from metric or fixed value
|
|
if (fixedWidthValue != null) {
|
|
// Use fixed width
|
|
const parsedFixedWidth = parseMetricValue(fixedWidthValue);
|
|
if (parsedFixedWidth !== undefined) {
|
|
feature.width = parsedFixedWidth;
|
|
}
|
|
} else if (widthMetricLabel && record[widthMetricLabel] != null) {
|
|
// Use metric value for width
|
|
const widthValue = parseMetricValue(record[widthMetricLabel]);
|
|
if (widthValue !== undefined) {
|
|
feature.width = widthValue;
|
|
}
|
|
}
|
|
|
|
if (categoryColumn && record[categoryColumn] != null) {
|
|
feature.cat_color = String(record[categoryColumn]);
|
|
}
|
|
|
|
feature = addJsColumnsToExtraProps(feature, record, jsColumns);
|
|
feature = addPropertiesToFeature(feature, record, excludeKeys);
|
|
return feature;
|
|
});
|
|
}
|
|
|
|
export default function transformProps(chartProps: ChartProps) {
|
|
const { rawFormData: formData } = chartProps;
|
|
const {
|
|
line_column,
|
|
line_type = 'json',
|
|
metric,
|
|
line_width,
|
|
dimension,
|
|
reverse_long_lat = false,
|
|
js_columns,
|
|
breakpoint_metric,
|
|
} = formData as DeckPathFormData;
|
|
|
|
// Check so legacy values still work
|
|
const fixedWidthValue =
|
|
typeof line_width === 'number'
|
|
? line_width
|
|
: isFixedValue(line_width)
|
|
? getFixedValue(line_width)
|
|
: undefined;
|
|
|
|
const widthMetricLabel = getMetricLabelFromFormData(line_width);
|
|
|
|
const breakpointMetricLabel = breakpoint_metric
|
|
? getMetricLabel(breakpoint_metric)
|
|
: undefined;
|
|
const baseMetricLabel = getMetricLabelFromFormData(metric);
|
|
const metricLabel = breakpointMetricLabel || baseMetricLabel;
|
|
|
|
// ensure all metric labels are included
|
|
const metricLabels = [
|
|
...(metricLabel ? [metricLabel] : []),
|
|
...(widthMetricLabel && widthMetricLabel !== metricLabel
|
|
? [widthMetricLabel]
|
|
: []),
|
|
];
|
|
|
|
const records = getRecordsFromQuery(chartProps.queriesData);
|
|
const features = processPathData(
|
|
records,
|
|
line_column || '',
|
|
line_type,
|
|
reverse_long_lat,
|
|
metricLabel,
|
|
js_columns,
|
|
widthMetricLabel,
|
|
fixedWidthValue,
|
|
dimension,
|
|
).reverse();
|
|
|
|
return createBaseTransformResult(chartProps, features, metricLabels);
|
|
}
|