mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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>
This commit is contained in:
parent
c8b2102500
commit
0bc314a47b
@ -1,4 +1,4 @@
|
|||||||
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()', () => {
|
|||||||
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[]
|
|||||||
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[]
|
|||||||
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', () => {
|
|||||||
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', () => {
|
|||||||
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', () => {
|
|||||||
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}',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +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) {
|
|||||||
regexp = input.replace(selectorRegexp, '').trim();
|
regexp = input.replace(selectorRegexp, '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (regexp) {
|
||||||
|
regexp = caseInsensitive + regexp;
|
||||||
|
}
|
||||||
return { query, regexp };
|
return { query, regexp };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user