[Grok Debugger] Adding syntax highlighting for grok expressions (#18572)

* Adding basic syntax highlighting for grok expressions

* Use EUI color palette

* Handle regex tokens, escaped and unescaped

* Return token for escaped content

* Add functional test

* Using higher-contrast colors

* Removing comment I used for developing the highlight rules

* Using object destructuring

* Removing unnecessary method
This commit is contained in:
Shaunak Kashyap 2018-04-30 12:31:23 -07:00
parent 617db3cbe8
commit 84ae548b77
7 changed files with 153 additions and 0 deletions

View file

@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import ace from 'ace';
const { TextHighlightRules } = ace.acequire("ace/mode/text_highlight_rules");
export class GrokHighlightRules extends TextHighlightRules {
constructor() {
super();
this.$rules = {
start: [
{
token: [
"grokStart",
"grokPatternName",
"grokSeparator",
"grokFieldName",
"grokEnd"
],
regex: "(%{)([^:]+)(:)([^:]+)(})"
},
{
token: [
"grokStart",
"grokPatternName",
"grokSeparator",
"grokFieldName",
"grokSeparator",
"grokFieldType",
"grokEnd"
],
regex: "(%{)([^:]+)(:)([^:]+)(:)([^:]+)(})"
},
{
token: (escapeToken, /* regexToken */) => {
if (escapeToken) {
return [ 'grokEscape', 'grokEscaped' ];
}
return 'grokRegex';
},
regex: "(\\\\)?([\\[\\]\\(\\)\\?\\:\\|])"
},
]
};
}
}

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import ace from 'ace';
import { GrokHighlightRules } from './grok_highlight_rules';
const TextMode = ace.acequire("ace/mode/text").Mode;
export class GrokMode extends TextMode {
constructor() {
super();
this.HighlightRules = GrokHighlightRules;
}
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { GrokMode } from './grok_mode';

View file

@ -18,4 +18,25 @@ grokdebugger {
.grokdebugger-simulate-button {
margin-bottom: 10px;
}
.ace_grokStart,
.ace_grokEnd,
.ace_grokSeparator,
.ace_grokEscape {
color: #999999; // euiColorMediumShade
}
.ace_grokPatternName {
color: #017F75; // euiColorSecondary
}
.ace_grokFieldName,
.ace_grokRegex {
color: #0079A5; // euiColorPrimary
}
.ace_grokFieldType {
color: #0079A5; // euiColorPrimary
font-style: italic;
}
}

View file

@ -7,6 +7,7 @@
import { uiModules } from 'ui/modules';
import template from './pattern_input.html';
import './pattern_input.less';
import { GrokMode } from '../../../../lib/ace';
const app = uiModules.get('xpack/grokdebugger');
@ -34,6 +35,7 @@ app.directive('patternInput', function () {
maxLines: 10
});
editor.$blockScrolling = Infinity;
editor.getSession().setMode(new GrokMode());
};
}
}

View file

@ -42,5 +42,43 @@ export default function ({ getService, getPageObjects }) {
await grokDebugger.assertEventOutput({ f: 'Seger', m: 'Comma', l: 'Bob' });
});
});
describe('syntax highlighting', () => {
it('applies the correct CSS classes', async () => {
const grokPattern = '\\[(?:-|%{NUMBER:bytes:int})\\]';
await grokDebugger.setPatternInput(grokPattern);
const GROK_START = 'grokStart';
const GROK_PATTERN_NAME = 'grokPatternName';
const GROK_SEPARATOR = 'grokSeparator';
const GROK_FIELD_NAME = 'grokFieldName';
const GROK_FIELD_TYPE = 'grokFieldType';
const GROK_END = 'grokEnd';
const GROK_ESCAPE = 'grokEscape';
const GROK_ESCAPED = 'grokEscaped';
const GROK_REGEX = 'grokRegex';
await grokDebugger.assertPatternInputSyntaxHighlighting([
{ token: GROK_ESCAPE, content: '\\' },
{ token: GROK_ESCAPED, content: '[' },
{ token: GROK_REGEX, content: '(' },
{ token: GROK_REGEX, content: '?' },
{ token: GROK_REGEX, content: ':' },
{ token: GROK_REGEX, content: '|' },
{ token: GROK_START, content: '%{' },
{ token: GROK_PATTERN_NAME, content: 'NUMBER' },
{ token: GROK_SEPARATOR, content: ':' },
{ token: GROK_FIELD_NAME, content: 'bytes' },
{ token: GROK_SEPARATOR, content: ':' },
{ token: GROK_FIELD_TYPE, content: 'int' },
{ token: GROK_END, content: '}' },
{ token: GROK_REGEX, content: ')' },
{ token: GROK_ESCAPE, content: '\\' },
{ token: GROK_ESCAPED, content: ']' },
]);
});
});
});
}

View file

@ -61,5 +61,23 @@ export function GrokDebuggerProvider({ getService }) {
expect(value).to.eql(expectedValue);
});
}
async assertPatternInputSyntaxHighlighting(expectedHighlights) {
const patternInputElement = await testSubjects.find(SUBJ_UI_ACE_PATTERN_INPUT);
const highlightedElements = await patternInputElement.findAllByXpath('.//div[@class="ace_line"]/*');
expect(highlightedElements.length).to.be(expectedHighlights.length);
await Promise.all(highlightedElements.map(async (element, index) => {
const highlightClass = await element.getAttribute('class');
const highlightedContent = await element.getVisibleText();
const expectedHighlight = expectedHighlights[index];
const expectedHighlightClass = `ace_${expectedHighlight.token}`;
const expectedHighlightedContent = expectedHighlight.content;
expect(highlightClass).to.be(expectedHighlightClass);
expect(highlightedContent).to.be(expectedHighlightedContent);
}));
}
};
}