diff --git a/public/app/core/utils/text.test.ts b/public/app/core/utils/text.test.ts index 206e8507f9d..624fbc6a1a0 100644 --- a/public/app/core/utils/text.test.ts +++ b/public/app/core/utils/text.test.ts @@ -1,4 +1,4 @@ -import { findMatchesInText } from './text'; +import { findMatchesInText, parseFlags } from './text'; describe('findMatchesInText()', () => { it('gets no matches for when search and or line are empty', () => { @@ -32,4 +32,37 @@ describe('findMatchesInText()', () => { expect(findMatchesInText('foo foo bar', '(')).toEqual([]); expect(findMatchesInText('foo foo bar', '(foo|')).toEqual([]); }); + + test('should parse and use flags', () => { + expect(findMatchesInText(' foo FOO bar ', '(?i)foo')).toEqual([ + { length: 3, start: 1, text: 'foo', end: 4 }, + { length: 3, start: 5, text: 'FOO', end: 8 }, + ]); + expect(findMatchesInText(' foo FOO bar ', '(?i)(?-i)foo')).toEqual([{ length: 3, start: 1, text: 'foo', end: 4 }]); + expect(findMatchesInText('FOO\nfoobar\nbar', '(?ims)^foo.')).toEqual([ + { length: 4, start: 0, text: 'FOO\n', end: 4 }, + { length: 4, start: 4, text: 'foob', end: 8 }, + ]); + expect(findMatchesInText('FOO\nfoobar\nbar', '(?ims)(?-smi)^foo.')).toEqual([]); + }); +}); + +describe('parseFlags()', () => { + it('when no flags or text', () => { + expect(parseFlags('')).toEqual({ cleaned: '', flags: 'g' }); + expect(parseFlags('(?is)')).toEqual({ cleaned: '', flags: 'gis' }); + expect(parseFlags('foo')).toEqual({ cleaned: 'foo', flags: 'g' }); + }); + + it('when flags present', () => { + expect(parseFlags('(?i)foo')).toEqual({ cleaned: 'foo', flags: 'gi' }); + expect(parseFlags('(?ims)foo')).toEqual({ cleaned: 'foo', flags: 'gims' }); + }); + + it('when flags cancel each other', () => { + expect(parseFlags('(?i)(?-i)foo')).toEqual({ cleaned: 'foo', flags: 'g' }); + expect(parseFlags('(?i-i)foo')).toEqual({ cleaned: 'foo', flags: 'g' }); + expect(parseFlags('(?is)(?-ims)foo')).toEqual({ cleaned: 'foo', flags: 'g' }); + expect(parseFlags('(?i)(?-i)(?i)foo')).toEqual({ cleaned: 'foo', flags: 'gi' }); + }); }); diff --git a/public/app/core/utils/text.ts b/public/app/core/utils/text.ts index bcd533ee51b..faf801b45be 100644 --- a/public/app/core/utils/text.ts +++ b/public/app/core/utils/text.ts @@ -22,10 +22,10 @@ export function findMatchesInText(haystack: string, needle: string): TextMatch[] return []; } const matches = []; - const cleaned = cleanNeedle(needle); + const { cleaned, flags } = parseFlags(cleanNeedle(needle)); let regexp: RegExp; try { - regexp = new RegExp(`(?:${cleaned})`, 'g'); + regexp = new RegExp(`(?:${cleaned})`, flags); } catch (error) { return matches; } @@ -44,6 +44,35 @@ export function findMatchesInText(haystack: string, needle: string): TextMatch[] return matches; } +const CLEAR_FLAG = '-'; +const FLAGS_REGEXP = /\(\?([ims-]+)\)/g; + +/** + * Converts any mode modifers in the text to the Javascript equivalent flag + */ +export function parseFlags(text: string): { cleaned: string; flags: string } { + const flags: Set = new Set(['g']); + + const cleaned = text.replace(FLAGS_REGEXP, (str, group) => { + const clearAll = group.startsWith(CLEAR_FLAG); + + for (let i = 0; i < group.length; ++i) { + const flag = group.charAt(i); + if (clearAll || group.charAt(i - 1) === CLEAR_FLAG) { + flags.delete(flag); + } else if (flag !== CLEAR_FLAG) { + flags.add(flag); + } + } + return ''; // Remove flag from text + }); + + return { + cleaned: cleaned, + flags: Array.from(flags).join(''), + }; +} + const XSSWL = Object.keys(xss.whiteList).reduce((acc, element) => { acc[element] = xss.whiteList[element].concat(['class', 'style']); return acc; diff --git a/public/app/plugins/datasource/loki/query_utils.test.ts b/public/app/plugins/datasource/loki/query_utils.test.ts index 21f1dbef07f..3685a3dd358 100644 --- a/public/app/plugins/datasource/loki/query_utils.test.ts +++ b/public/app/plugins/datasource/loki/query_utils.test.ts @@ -11,7 +11,7 @@ describe('parseQuery', () => { it('returns regexp for strings without query', () => { expect(parseQuery('test')).toEqual({ query: '', - regexp: 'test', + regexp: '(?i)test', }); }); @@ -25,14 +25,14 @@ describe('parseQuery', () => { it('returns query for strings with query and search string', () => { expect(parseQuery('x {foo="bar"}')).toEqual({ query: '{foo="bar"}', - regexp: 'x', + regexp: '(?i)x', }); }); it('returns query for strings with query and regexp', () => { expect(parseQuery('{foo="bar"} x|y')).toEqual({ query: '{foo="bar"}', - regexp: 'x|y', + regexp: '(?i)x|y', }); }); @@ -46,11 +46,11 @@ describe('parseQuery', () => { it('returns query and regexp with quantifiers', () => { expect(parseQuery('{foo="bar"} \\.java:[0-9]{1,5}')).toEqual({ query: '{foo="bar"}', - regexp: '\\.java:[0-9]{1,5}', + regexp: '(?i)\\.java:[0-9]{1,5}', }); expect(parseQuery('\\.java:[0-9]{1,5} {foo="bar"}')).toEqual({ query: '{foo="bar"}', - regexp: '\\.java:[0-9]{1,5}', + regexp: '(?i)\\.java:[0-9]{1,5}', }); }); }); diff --git a/public/app/plugins/datasource/loki/query_utils.ts b/public/app/plugins/datasource/loki/query_utils.ts index 4f246ea8e28..7d2c6234f05 100644 --- a/public/app/plugins/datasource/loki/query_utils.ts +++ b/public/app/plugins/datasource/loki/query_utils.ts @@ -1,4 +1,5 @@ const selectorRegexp = /(?:^|\s){[^{]*}/g; +const caseInsensitive = '(?i)'; // Golang mode modifier for Loki, doesn't work in JavaScript export function parseQuery(input: string) { input = input || ''; const match = input.match(selectorRegexp); @@ -10,6 +11,9 @@ export function parseQuery(input: string) { regexp = input.replace(selectorRegexp, '').trim(); } + if (regexp) { + regexp = caseInsensitive + regexp; + } return { query, regexp }; }