Skip to content

Commit 0f5204f

Browse files
[ML] Data Frame Analytics: Fix special character escaping for Vega scatterplot matrix. (#98763) (#98893)
- Fixes correctly escaping the characters .[] in field names with double backslashes since Vega treats dots/brackets in field names special to be able to access attributes in object structures. This replaces the old approach that replaced dots with a similar but different UTF-8 character but missed the brackets. - Additionally adds an EuiErrorBoundary component around the VegaChart component so we don't crash the whole page should another issue with Vega bubble up. Co-authored-by: Walter Rafelsberger <walter@elastic.co>
1 parent 22ea0f0 commit 0f5204f

File tree

3 files changed

+44
-31
lines changed

3 files changed

+44
-31
lines changed

x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* 2.0.
66
*/
77

8+
import 'jest-canvas-mock';
9+
810
// @ts-ignore
911
import { compile } from 'vega-lite/build/vega-lite';
1012

@@ -100,8 +102,8 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => {
100102
});
101103
expect(vegaLiteSpec.spec.encoding.color).toEqual({
102104
condition: {
103-
// Note the alternative UTF-8 dot character
104-
test: "(datum['mloutlier_score'] >= mlOutlierScoreThreshold.cutoff)",
105+
// Note the escaped dot character
106+
test: "(datum['ml\\.outlier_score'] >= mlOutlierScoreThreshold.cutoff)",
105107
value: COLOR_OUTLIER,
106108
},
107109
value: euiThemeLight.euiColorMediumShade,
@@ -110,8 +112,8 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => {
110112
{ field: 'x', type: 'quantitative' },
111113
{ field: 'y', type: 'quantitative' },
112114
{
113-
// Note the alternative UTF-8 dot character
114-
field: 'mloutlier_score',
115+
// Note the escaped dot character
116+
field: 'ml\\.outlier_score',
115117
format: '.3f',
116118
type: 'quantitative',
117119
},
@@ -156,4 +158,25 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => {
156158
{ field: 'y', type: 'quantitative' },
157159
]);
158160
});
161+
162+
it('should escape special characters', () => {
163+
const data = [{ ['x.a']: 1, ['y[a]']: 1 }];
164+
165+
const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec(
166+
data,
167+
['x.a', 'y[a]'],
168+
euiThemeLight,
169+
undefined,
170+
'the-color-field',
171+
LEGEND_TYPES.NOMINAL
172+
);
173+
174+
// column values should be escaped
175+
expect(vegaLiteSpec.repeat).toEqual({
176+
column: ['x\\.a', 'y\\[a\\]'],
177+
row: ['y\\[a\\]', 'x\\.a'],
178+
});
179+
// raw data should not be escaped
180+
expect(vegaLiteSpec.spec.data.values).toEqual(data);
181+
});
159182
});

x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -59,26 +59,11 @@ export const getColorSpec = (
5959
return { value: DEFAULT_COLOR };
6060
};
6161

62-
// Replace dots in field names with an alternative UTF-8 character
63-
// since VEGA treats dots in field names as nested values and escaping
64-
// in columns/rows for repeated charts isn't working as expected.
62+
// Escapes the characters .[] in field names with double backslashes
63+
// since VEGA treats dots/brackets in field names as nested values.
64+
// See https://vega.github.io/vega-lite/docs/field.html for details.
6565
function getEscapedVegaFieldName(fieldName: string) {
66-
return fieldName.replace(/\./g, '․');
67-
}
68-
69-
// Replace dots for all keys of all data items with an alternative UTF-8 character
70-
// since VEGA treats dots in field names as nested values and escaping
71-
// in columns/rows for repeated charts isn't working as expected.
72-
function getEscapedVegaValues(values: VegaValue[]): VegaValue[] {
73-
return values.map((d) =>
74-
Object.keys(d).reduce(
75-
(p, c) => ({
76-
...p,
77-
[getEscapedVegaFieldName(c)]: d[c],
78-
}),
79-
{} as VegaValue
80-
)
81-
);
66+
return fieldName.replace(/([\.|\[|\]])/g, '\\$1');
8267
}
8368

8469
type VegaValue = Record<string, string | number>;
@@ -92,13 +77,11 @@ export const getScatterplotMatrixVegaLiteSpec = (
9277
legendType?: LegendType,
9378
dynamicSize?: boolean
9479
): TopLevelSpec => {
95-
const vegaValues = getEscapedVegaValues(values);
80+
const vegaValues = values;
9681
const vegaColumns = columns.map(getEscapedVegaFieldName);
9782
const outliers = resultsField !== undefined;
9883

99-
// Use an alternative UTF-8 character for the dot
100-
// since VEGA treats dots in field names as nested values.
101-
const escapedOutlierScoreField = `${resultsField}${OUTLIER_SCORE_FIELD}`;
84+
const escapedOutlierScoreField = `${resultsField}\\.${OUTLIER_SCORE_FIELD}`;
10285

10386
const colorSpec = getColorSpec(
10487
euiTheme,
@@ -193,7 +176,10 @@ export const getScatterplotMatrixVegaLiteSpec = (
193176
...(color !== undefined
194177
? [{ type: colorSpec.type, field: getEscapedVegaFieldName(color) }]
195178
: []),
196-
...vegaColumns.map((d) => ({ type: LEGEND_TYPES.QUANTITATIVE, field: d })),
179+
...vegaColumns.map((d) => ({
180+
type: LEGEND_TYPES.QUANTITATIVE,
181+
field: d,
182+
})),
197183
...(outliers
198184
? [{ type: LEGEND_TYPES.QUANTITATIVE, field: escapedOutlierScoreField, format: '.3f' }]
199185
: []),

x-pack/plugins/ml/public/application/components/vega_chart/vega_chart.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77

88
import React, { FC, Suspense } from 'react';
99

10+
import { EuiErrorBoundary } from '@elastic/eui';
11+
1012
import { VegaChartLoading } from './vega_chart_loading';
1113
import type { VegaChartViewProps } from './vega_chart_view';
1214

1315
const VegaChartView = React.lazy(() => import('./vega_chart_view'));
1416

1517
export const VegaChart: FC<VegaChartViewProps> = (props) => (
16-
<Suspense fallback={<VegaChartLoading />}>
17-
<VegaChartView {...props} />
18-
</Suspense>
18+
<EuiErrorBoundary>
19+
<Suspense fallback={<VegaChartLoading />}>
20+
<VegaChartView {...props} />
21+
</Suspense>
22+
</EuiErrorBoundary>
1923
);

0 commit comments

Comments
 (0)