Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
tooltipContents,
tooltipTemplate,
jsFunctionControl,
crossFilterColumn,
} from '../../utilities/Shared_DeckGL';
import { dndGeojsonColumn } from '../../utilities/sharedDndControls';
import { BLACK_COLOR } from '../../utilities/controls';
Expand Down Expand Up @@ -367,6 +368,7 @@ const config: ControlPanelConfig = {
{
label: t('Advanced'),
controlSetRows: [
[crossFilterColumn],
Comment thread
alex-poor marked this conversation as resolved.
[jsColumns],
[jsDataMutator],
[jsTooltip],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ 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;
}
Expand All @@ -58,6 +59,7 @@ export default function buildQuery(formData: DeckPolygonFormData) {
point_radius_fixed,
filter_nulls = true,
js_columns,
cross_filter_column,
tooltip_contents,
} = formData;

Expand All @@ -78,6 +80,10 @@ export default function buildQuery(formData: DeckPolygonFormData) {
}
});

if (cross_filter_column && !columns.includes(cross_filter_column)) {
columns.push(cross_filter_column);
}

columns = addTooltipColumnsToQuery(columns, tooltip_contents);

const metrics = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
jsDataMutator,
jsTooltip,
jsOnclickHref,
crossFilterColumn,
legendFormat,
legendPosition,
fillColorPicker,
Expand Down Expand Up @@ -203,6 +204,7 @@ const config: ControlPanelConfig = {
{
label: t('Advanced'),
controlSetRows: [
[crossFilterColumn],
[jsColumns],
[jsDataMutator],
[jsTooltip],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,21 @@ export function commonLayerProps({
formData,
});

if (event.leftButton && setDataMask !== undefined && crossFilters) {
// deck.gl v9 event shape: { type, offsetCenter, srcEvent, tapCount }.
// Older code checked event.leftButton / event.rightButton which no
// longer exist; dispatch on event.type and the underlying MouseEvent
// button instead.
const srcEvent = event?.srcEvent;
const isContextMenu =
event?.type === 'contextmenu' || srcEvent?.button === 2;
const isLeftClick =
event?.type === 'click' && (srcEvent?.button ?? 0) === 0;

if (isLeftClick && setDataMask !== undefined && crossFilters) {
setDataMask(crossFilters.dataMask);
} else if (event.rightButton && onContextMenu !== undefined) {
onContextMenu(event.center.x, event.center.y, {
} else if (isContextMenu && onContextMenu !== undefined) {
const center = event?.offsetCenter ?? event?.center ?? { x: 0, y: 0 };
onContextMenu(center.x, center.y, {
drillToDetail: [],
crossFilter: crossFilters,
drillBy: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,21 @@ export const jsColumns = {
},
};

export const crossFilterColumn: CustomControlItem = {
name: 'cross_filter_column',
config: {
...sharedControls.groupby,
label: t('Cross-filter column'),
multi: false,
default: null,
description: t(
'Dimension column emitted as a cross-filter when a feature is clicked. ' +
'Other charts on the dashboard match against this column. If unset, ' +
'falls back to the geometry column (legacy behavior, often unmatchable).',
),
},
};

export const jsDataMutator = {
name: 'js_data_mutator',
config: jsFunctionControl(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,293 @@ describe('getCrossFilterDataMask', () => {
expect(dataMask).toStrictEqual(expected);
});

test('deck_polygon with cross_filter_column emits dimension filter (top-level)', () => {
const polygonFormData = {
...formData,
line_column: 'geojson',
cross_filter_column: 'sa3_name',
};

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],
],
sa3_name: 'Christchurch West',
extraProps: {},
},
};

const dataMask = getCrossFilterDataMask({
formData: polygonFormData,
data: polygonPickingData,
filterState: {},
});

expect(dataMask).toStrictEqual({
dataMask: {
extraFormData: {
filters: [
{
col: 'sa3_name',
op: '==',
val: 'Christchurch West',
},
],
},
filterState: {
value: ['Christchurch West'],
},
},
isCurrentValueSelected: false,
});
});

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 falls back to legacy when cross_filter_column value missing on feature', () => {
const polygonFormData = {
...formData,
line_column: 'geojson',
cross_filter_column: 'sa3_name',
};

const polygonPickingData = {
...pickingData,
object: {
polygon: 'POLYGON_PATH_STRING',
// sa3_name intentionally absent (e.g. chart was saved before the column
// was added to the query)
extraProps: {},
},
};

const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const dataMask = getCrossFilterDataMask({
formData: polygonFormData,
data: polygonPickingData,
filterState: {},
});
expect(warnSpy).toHaveBeenCalledTimes(1);
warnSpy.mockRestore();

expect(dataMask).toStrictEqual({
dataMask: {
extraFormData: {
filters: [
{
col: {
expressionType: 'SQL',
sqlExpression: "REPLACE(geojson, ' ', '')",
label: 'geojson',
},
op: '==',
val: '"POLYGON_PATH_STRING"',
},
],
},
filterState: {
value: ['"POLYGON_PATH_STRING"'],
},
},
isCurrentValueSelected: false,
});
});

test('deck_polygon without cross_filter_column falls back to legacy geometry filter', () => {
const polygonFormData = {
...formData,
line_column: 'geojson',
};

const polygonPickingData = {
...pickingData,
object: {
polygon: 'POLYGON_PATH_STRING',
},
};

const dataMask = getCrossFilterDataMask({
formData: polygonFormData,
data: polygonPickingData,
filterState: {},
});

expect(dataMask).toStrictEqual({
dataMask: {
extraFormData: {
filters: [
{
col: {
expressionType: 'SQL',
sqlExpression: "REPLACE(geojson, ' ', '')",
label: 'geojson',
},
op: '==',
val: '"POLYGON_PATH_STRING"',
},
],
},
filterState: {
value: ['"POLYGON_PATH_STRING"'],
},
},
isCurrentValueSelected: false,
});
});

test('deck_geojson with cross_filter_column emits filter from feature.properties', () => {
const geojsonFormData = {
...formData,
geojson: 'shape',
cross_filter_column: 'sa3_name',
};

const geojsonPickingData = {
...pickingData,
object: {
type: 'Feature',
properties: { sa3_name: 'Christchurch West', sa3_code: '301' },
geometry: {
type: 'Polygon',
coordinates: [
[
[-122.42, 37.8],
[-122.42, 37.81],
[-122.41, 37.81],
[-122.41, 37.8],
[-122.42, 37.8],
],
],
},
},
};

const dataMask = getCrossFilterDataMask({
formData: geojsonFormData,
data: geojsonPickingData,
filterState: {},
});

expect(dataMask).toStrictEqual({
dataMask: {
extraFormData: {
filters: [
{
col: 'sa3_name',
op: '==',
val: 'Christchurch West',
},
],
},
filterState: {
value: ['Christchurch West'],
},
},
isCurrentValueSelected: false,
});
});

test('deck_geojson without cross_filter_column falls back to legacy LIKE filter', () => {
const geojsonFormData = {
...formData,
geojson: 'shape',
};

const coords = [
[
[-122.42, 37.8],
[-122.42, 37.81],
],
];

const geojsonPickingData = {
...pickingData,
object: {
type: 'Feature',
properties: {},
geometry: { type: 'Polygon', coordinates: coords },
},
};

const dataMask = getCrossFilterDataMask({
formData: geojsonFormData,
data: geojsonPickingData,
filterState: {},
});

expect(dataMask).toStrictEqual({
dataMask: {
extraFormData: {
filters: [
{
col: {
expressionType: 'SQL',
sqlExpression: "REPLACE(shape, ' ', '')",
label: 'shape',
},
op: 'LIKE',
val: `%${JSON.stringify(coords)}%`,
},
],
},
filterState: {
value: [coords],
},
},
isCurrentValueSelected: false,
});
});

test('handles Charts with GPU aggregation', () => {
const latlongGPUFormData = {
...formData,
Expand Down
Loading
Loading