Commit 0bc314a4 by Steven Sheehy Committed by David

Feature: Case insensitive Loki search (#15948)

* Case insensitive Loki search
* Make Loki case insensitivity work with highlighting

Signed-off-by: Steven Sheehy <ssheehy@firescope.com>
parent c8b21025
import { findMatchesInText } from './text'; import { findMatchesInText, parseFlags } from './text';
describe('findMatchesInText()', () => { describe('findMatchesInText()', () => {
it('gets no matches for when search and or line are empty', () => { it('gets no matches for when search and or line are empty', () => {
...@@ -32,4 +32,37 @@ describe('findMatchesInText()', () => { ...@@ -32,4 +32,37 @@ describe('findMatchesInText()', () => {
expect(findMatchesInText('foo foo bar', '(')).toEqual([]); expect(findMatchesInText('foo foo bar', '(')).toEqual([]);
expect(findMatchesInText('foo foo bar', '(foo|')).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' });
});
}); });
...@@ -22,10 +22,10 @@ export function findMatchesInText(haystack: string, needle: string): TextMatch[] ...@@ -22,10 +22,10 @@ export function findMatchesInText(haystack: string, needle: string): TextMatch[]
return []; return [];
} }
const matches = []; const matches = [];
const cleaned = cleanNeedle(needle); const { cleaned, flags } = parseFlags(cleanNeedle(needle));
let regexp: RegExp; let regexp: RegExp;
try { try {
regexp = new RegExp(`(?:${cleaned})`, 'g'); regexp = new RegExp(`(?:${cleaned})`, flags);
} catch (error) { } catch (error) {
return matches; return matches;
} }
...@@ -44,6 +44,35 @@ export function findMatchesInText(haystack: string, needle: string): TextMatch[] ...@@ -44,6 +44,35 @@ export function findMatchesInText(haystack: string, needle: string): TextMatch[]
return matches; 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<string> = 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) => { const XSSWL = Object.keys(xss.whiteList).reduce((acc, element) => {
acc[element] = xss.whiteList[element].concat(['class', 'style']); acc[element] = xss.whiteList[element].concat(['class', 'style']);
return acc; return acc;
......
...@@ -11,7 +11,7 @@ describe('parseQuery', () => { ...@@ -11,7 +11,7 @@ describe('parseQuery', () => {
it('returns regexp for strings without query', () => { it('returns regexp for strings without query', () => {
expect(parseQuery('test')).toEqual({ expect(parseQuery('test')).toEqual({
query: '', query: '',
regexp: 'test', regexp: '(?i)test',
}); });
}); });
...@@ -25,14 +25,14 @@ describe('parseQuery', () => { ...@@ -25,14 +25,14 @@ describe('parseQuery', () => {
it('returns query for strings with query and search string', () => { it('returns query for strings with query and search string', () => {
expect(parseQuery('x {foo="bar"}')).toEqual({ expect(parseQuery('x {foo="bar"}')).toEqual({
query: '{foo="bar"}', query: '{foo="bar"}',
regexp: 'x', regexp: '(?i)x',
}); });
}); });
it('returns query for strings with query and regexp', () => { it('returns query for strings with query and regexp', () => {
expect(parseQuery('{foo="bar"} x|y')).toEqual({ expect(parseQuery('{foo="bar"} x|y')).toEqual({
query: '{foo="bar"}', query: '{foo="bar"}',
regexp: 'x|y', regexp: '(?i)x|y',
}); });
}); });
...@@ -46,11 +46,11 @@ describe('parseQuery', () => { ...@@ -46,11 +46,11 @@ describe('parseQuery', () => {
it('returns query and regexp with quantifiers', () => { it('returns query and regexp with quantifiers', () => {
expect(parseQuery('{foo="bar"} \\.java:[0-9]{1,5}')).toEqual({ expect(parseQuery('{foo="bar"} \\.java:[0-9]{1,5}')).toEqual({
query: '{foo="bar"}', 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({ expect(parseQuery('\\.java:[0-9]{1,5} {foo="bar"}')).toEqual({
query: '{foo="bar"}', query: '{foo="bar"}',
regexp: '\\.java:[0-9]{1,5}', regexp: '(?i)\\.java:[0-9]{1,5}',
}); });
}); });
}); });
const selectorRegexp = /(?:^|\s){[^{]*}/g; const selectorRegexp = /(?:^|\s){[^{]*}/g;
const caseInsensitive = '(?i)'; // Golang mode modifier for Loki, doesn't work in JavaScript
export function parseQuery(input: string) { export function parseQuery(input: string) {
input = input || ''; input = input || '';
const match = input.match(selectorRegexp); const match = input.match(selectorRegexp);
...@@ -10,6 +11,9 @@ export function parseQuery(input: string) { ...@@ -10,6 +11,9 @@ export function parseQuery(input: string) {
regexp = input.replace(selectorRegexp, '').trim(); regexp = input.replace(selectorRegexp, '').trim();
} }
if (regexp) {
regexp = caseInsensitive + regexp;
}
return { query, regexp }; return { query, regexp };
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment