diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_context_menu.tsx b/x-pack/plugins/ml/public/application/explorer/anomaly_context_menu.tsx index 719c71a3b853f..bb1427c867b92 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomaly_context_menu.tsx +++ b/x-pack/plugins/ml/public/application/explorer/anomaly_context_menu.tsx @@ -40,7 +40,7 @@ import { JobId } from '../../../common/types/anomaly_detection_jobs'; import { getDefaultExplorerChartsPanelTitle } from '../../embeddables/anomaly_charts/anomaly_charts_embeddable'; import { MAX_ANOMALY_CHARTS_ALLOWED } from '../../embeddables/anomaly_charts/anomaly_charts_initializer'; import { useAnomalyExplorerContext } from './anomaly_explorer_context'; -import { escapeKueryForFieldValuePair } from '../util/string_utils'; +import { escapeKueryForEmbeddableFieldValuePair } from '../util/string_utils'; import { useCasesModal } from '../contexts/kibana/use_cases_modal'; import { DEFAULT_MAX_SERIES_TO_PLOT } from '../services/anomaly_explorer_charts_service'; import { @@ -154,7 +154,7 @@ export const AnomalyContextMenu: FC = ({ const influencers = selectionInfluencers ?? []; const config = getDefaultEmbeddablePanelConfig(jobIds, queryString); const queryFromSelectedCells = influencers - .map((s) => escapeKueryForFieldValuePair(s.fieldName, s.fieldValue)) + .map((s) => escapeKueryForEmbeddableFieldValuePair(s.fieldName, s.fieldValue)) .join(' or '); // When adding anomaly charts to Dashboard, we want to respect the Dashboard's time range diff --git a/x-pack/plugins/ml/public/application/util/string_utils.test.ts b/x-pack/plugins/ml/public/application/util/string_utils.test.ts index 94463e38f9611..3ee52810f243a 100644 --- a/x-pack/plugins/ml/public/application/util/string_utils.test.ts +++ b/x-pack/plugins/ml/public/application/util/string_utils.test.ts @@ -14,6 +14,7 @@ import { toLocaleString, mlEscape, escapeForElasticsearchQuery, + escapeKueryForEmbeddableFieldValuePair, } from './string_utils'; describe('ML - string utils', () => { @@ -160,4 +161,13 @@ describe('ML - string utils', () => { expect(escapeForElasticsearchQuery('foo/bar')).toBe('foo\\/bar'); }); }); + describe('escapeKueryForEmbeddableFieldValuePair', () => { + test('should return correct escaping of kuery values', () => { + expect(escapeKueryForEmbeddableFieldValuePair('fieldName', '')).toBe('fieldName:""'); + expect(escapeKueryForEmbeddableFieldValuePair('', 'fieldValue')).toBe('"":fieldValue'); + expect(escapeKueryForEmbeddableFieldValuePair('@#specialCharsName%', '<>:;[})')).toBe( + '@#specialCharsName%:\\<\\>\\:;[}\\)' + ); + }); + }); }); diff --git a/x-pack/plugins/ml/public/application/util/string_utils.ts b/x-pack/plugins/ml/public/application/util/string_utils.ts index c5c0245478214..a552ea9f25109 100644 --- a/x-pack/plugins/ml/public/application/util/string_utils.ts +++ b/x-pack/plugins/ml/public/application/util/string_utils.ts @@ -140,6 +140,26 @@ export function escapeKueryForFieldValuePair( return `${escapeKuery(name)}:${escapeKuery(value.toString())}`; } +const replaceEmptyStringWithQuotation = (s: string) => (s === '' ? '""' : s); + +/** + * + * Helper function to returns escaped combined field name and value + * which also replaces empty str with " to ensure compatability with kql queries + * @param name fieldName of selection + * @param value fieldValue of selection + * @returns {string} escaped `name:value` compatible with embeddable input + */ +export function escapeKueryForEmbeddableFieldValuePair( + name: string, + value: string | number | boolean | undefined +): string { + if (!isDefined(name) || !isDefined(value)) return ''; + return `${replaceEmptyStringWithQuotation(escapeKuery(name))}:${replaceEmptyStringWithQuotation( + escapeKuery(value.toString()) + )}`; +} + export function calculateTextWidth(txt: string | number, isNumber: boolean) { txt = isNumber && typeof txt === 'number' ? d3.format(',')(txt) : txt;