mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Azure Monitor: replace monaco by slate with initial Kusto syntax
This commit is contained in:
parent
96ffa9d797
commit
fefb2c2ba2
@ -11,7 +11,6 @@
|
||||
"url": "http://github.com/grafana/grafana.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@alexanderzobnin/monaco-kusto": "^0.2.3-rc.1",
|
||||
"@babel/core": "^7.1.2",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/preset-env": "^7.1.0",
|
||||
@ -99,7 +98,6 @@
|
||||
"tslint-react": "^3.6.0",
|
||||
"typescript": "^3.0.3",
|
||||
"uglifyjs-webpack-plugin": "^1.2.7",
|
||||
"vscode-languageserver-types": "^3.14.0",
|
||||
"webpack": "4.19.1",
|
||||
"webpack-bundle-analyzer": "^2.9.0",
|
||||
"webpack-cleanup-plugin": "^0.5.1",
|
||||
|
@ -0,0 +1,348 @@
|
||||
import Plain from 'slate-plain-serializer';
|
||||
|
||||
import QueryField from './query_field';
|
||||
// import debounce from './utils/debounce';
|
||||
// import {getNextCharacter} from './utils/dom';
|
||||
import debounce from 'app/features/explore/utils/debounce';
|
||||
import { getNextCharacter } from 'app/features/explore/utils/dom';
|
||||
|
||||
import { FUNCTIONS, KEYWORDS } from './kusto';
|
||||
// import '../sass/editor.base.scss';
|
||||
|
||||
|
||||
const TYPEAHEAD_DELAY = 500;
|
||||
|
||||
interface Suggestion {
|
||||
text: string;
|
||||
deleteBackwards?: number;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
interface SuggestionGroup {
|
||||
label: string;
|
||||
items: Suggestion[];
|
||||
prefixMatch?: boolean;
|
||||
skipFilter?: boolean;
|
||||
}
|
||||
|
||||
const cleanText = s => s.replace(/[{}[\]="(),!~+\-*/^%]/g, '').trim();
|
||||
const wrapText = text => ({ text });
|
||||
|
||||
export default class KustoQueryField extends QueryField {
|
||||
fields: any;
|
||||
events: any;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.onTypeahead = debounce(this.onTypeahead, TYPEAHEAD_DELAY);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateMenu();
|
||||
}
|
||||
|
||||
onTypeahead = () => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.anchorNode) {
|
||||
const wrapperNode = selection.anchorNode.parentElement;
|
||||
if (wrapperNode === null) {
|
||||
return;
|
||||
}
|
||||
const editorNode = wrapperNode.closest('.slate-query-field');
|
||||
if (!editorNode || this.state.value.isBlurred) {
|
||||
// Not inside this editor
|
||||
return;
|
||||
}
|
||||
|
||||
// DOM ranges
|
||||
const range = selection.getRangeAt(0);
|
||||
const text = selection.anchorNode.textContent;
|
||||
if (text === null) {
|
||||
return;
|
||||
}
|
||||
const offset = range.startOffset;
|
||||
let prefix = cleanText(text.substr(0, offset));
|
||||
|
||||
// Model ranges
|
||||
const modelOffset = this.state.value.anchorOffset;
|
||||
const modelPrefix = this.state.value.anchorText.text.slice(0, modelOffset);
|
||||
|
||||
// Determine candidates by context
|
||||
let suggestionGroups: SuggestionGroup[] = [];
|
||||
const wrapperClasses = wrapperNode.classList;
|
||||
let typeaheadContext: string | null = null;
|
||||
|
||||
if (wrapperClasses.contains('function-context')) {
|
||||
typeaheadContext = 'context-function';
|
||||
if (this.fields) {
|
||||
suggestionGroups = this._getFieldsSuggestions();
|
||||
} else {
|
||||
this._fetchFields();
|
||||
return;
|
||||
}
|
||||
} else if (modelPrefix.match(/(facet\s$)/i)) {
|
||||
typeaheadContext = 'context-facet';
|
||||
if (this.fields) {
|
||||
suggestionGroups = this._getFieldsSuggestions();
|
||||
} else {
|
||||
this._fetchFields();
|
||||
return;
|
||||
}
|
||||
} else if (modelPrefix.match(/(,\s*$)/)) {
|
||||
typeaheadContext = 'context-multiple-fields';
|
||||
if (this.fields) {
|
||||
suggestionGroups = this._getFieldsSuggestions();
|
||||
} else {
|
||||
this._fetchFields();
|
||||
return;
|
||||
}
|
||||
} else if (modelPrefix.match(/(from\s$)/i)) {
|
||||
typeaheadContext = 'context-from';
|
||||
if (this.events) {
|
||||
suggestionGroups = this._getAfterFromSuggestions();
|
||||
} else {
|
||||
this._fetchEvents();
|
||||
return;
|
||||
}
|
||||
} else if (modelPrefix.match(/(^select\s\w*$)/i)) {
|
||||
typeaheadContext = 'context-select';
|
||||
if (this.fields) {
|
||||
suggestionGroups = this._getAfterSelectSuggestions();
|
||||
} else {
|
||||
this._fetchFields();
|
||||
return;
|
||||
}
|
||||
} else if (
|
||||
modelPrefix.match(/\)\s$/) ||
|
||||
modelPrefix.match(/SELECT ((?:\$?\w+\(?\w*\)?\,?\s*)+)([\)]\s|\b)/gi)
|
||||
) {
|
||||
typeaheadContext = 'context-after-function';
|
||||
suggestionGroups = this._getAfterFunctionSuggestions();
|
||||
} else if (modelPrefix.match(/from\s\S+\s\w*$/i)) {
|
||||
prefix = '';
|
||||
typeaheadContext = 'context-since';
|
||||
suggestionGroups = this._getAfterEventSuggestions();
|
||||
// } else if (modelPrefix.match(/\d+\s\w*$/)) {
|
||||
// typeaheadContext = 'context-number';
|
||||
// suggestionGroups = this._getAfterNumberSuggestions();
|
||||
} else if (modelPrefix.match(/ago\b/i) || modelPrefix.match(/facet\b/i) || modelPrefix.match(/\$__timefilter\b/i)) {
|
||||
typeaheadContext = 'context-timeseries';
|
||||
suggestionGroups = this._getAfterAgoSuggestions();
|
||||
} else if (prefix && !wrapperClasses.contains('argument')) {
|
||||
typeaheadContext = 'context-builtin';
|
||||
suggestionGroups = this._getKeywordSuggestions();
|
||||
} else if (Plain.serialize(this.state.value) === '') {
|
||||
typeaheadContext = 'context-new';
|
||||
suggestionGroups = this._getInitialSuggestions();
|
||||
}
|
||||
|
||||
let results = 0;
|
||||
prefix = prefix.toLowerCase();
|
||||
const filteredSuggestions = suggestionGroups.map(group => {
|
||||
if (group.items && prefix && !group.skipFilter) {
|
||||
group.items = group.items.filter(c => c.text.length >= prefix.length);
|
||||
if (group.prefixMatch) {
|
||||
group.items = group.items.filter(c => c.text.toLowerCase().indexOf(prefix) === 0);
|
||||
} else {
|
||||
group.items = group.items.filter(c => c.text.toLowerCase().indexOf(prefix) > -1);
|
||||
}
|
||||
}
|
||||
results += group.items.length;
|
||||
return group;
|
||||
})
|
||||
.filter(group => group.items.length > 0);
|
||||
|
||||
// console.log('onTypeahead', selection.anchorNode, wrapperClasses, text, offset, prefix, typeaheadContext);
|
||||
|
||||
this.setState({
|
||||
typeaheadPrefix: prefix,
|
||||
typeaheadContext,
|
||||
typeaheadText: text,
|
||||
suggestions: results > 0 ? filteredSuggestions : [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
applyTypeahead(change, suggestion) {
|
||||
const { typeaheadPrefix, typeaheadContext, typeaheadText } = this.state;
|
||||
let suggestionText = suggestion.text || suggestion;
|
||||
const move = 0;
|
||||
|
||||
// Modify suggestion based on context
|
||||
|
||||
const nextChar = getNextCharacter();
|
||||
if (suggestion.type === 'function') {
|
||||
if (!nextChar || nextChar !== '(') {
|
||||
suggestionText += '(';
|
||||
}
|
||||
} else if (typeaheadContext === 'context-function') {
|
||||
if (!nextChar || nextChar !== ')') {
|
||||
suggestionText += ')';
|
||||
}
|
||||
} else {
|
||||
if (!nextChar || nextChar !== ' ') {
|
||||
suggestionText += ' ';
|
||||
}
|
||||
}
|
||||
|
||||
this.resetTypeahead();
|
||||
|
||||
// Remove the current, incomplete text and replace it with the selected suggestion
|
||||
const backward = suggestion.deleteBackwards || typeaheadPrefix.length;
|
||||
const text = cleanText(typeaheadText);
|
||||
const suffixLength = text.length - typeaheadPrefix.length;
|
||||
const offset = typeaheadText.indexOf(typeaheadPrefix);
|
||||
const midWord = typeaheadPrefix && ((suffixLength > 0 && offset > -1) || suggestionText === typeaheadText);
|
||||
const forward = midWord ? suffixLength + offset : 0;
|
||||
|
||||
return change
|
||||
.deleteBackward(backward)
|
||||
.deleteForward(forward)
|
||||
.insertText(suggestionText)
|
||||
.move(move)
|
||||
.focus();
|
||||
}
|
||||
|
||||
private _getFieldsSuggestions(): SuggestionGroup[] {
|
||||
return [
|
||||
{
|
||||
prefixMatch: true,
|
||||
label: 'Fields',
|
||||
items: this.fields.map(wrapText)
|
||||
},
|
||||
{
|
||||
prefixMatch: true,
|
||||
label: 'Variables',
|
||||
items: this.props.templateVariables.map(wrapText)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private _getAfterFromSuggestions(): SuggestionGroup[] {
|
||||
return [
|
||||
{
|
||||
skipFilter: true,
|
||||
label: 'Events',
|
||||
items: this.events.map(wrapText)
|
||||
},
|
||||
{
|
||||
prefixMatch: true,
|
||||
label: 'Variables',
|
||||
items: this.props.templateVariables
|
||||
.map(wrapText)
|
||||
.map(suggestion => {
|
||||
suggestion.deleteBackwards = 0;
|
||||
return suggestion;
|
||||
})
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private _getAfterSelectSuggestions(): SuggestionGroup[] {
|
||||
return [
|
||||
{
|
||||
prefixMatch: true,
|
||||
label: 'Fields',
|
||||
items: this.fields.map(wrapText)
|
||||
},
|
||||
{
|
||||
prefixMatch: true,
|
||||
label: 'Functions',
|
||||
items: FUNCTIONS.map((s: any) => { s.type = 'function'; return s; })
|
||||
},
|
||||
{
|
||||
prefixMatch: true,
|
||||
label: 'Variables',
|
||||
items: this.props.templateVariables.map(wrapText)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private _getAfterFunctionSuggestions(): SuggestionGroup[] {
|
||||
return [{
|
||||
prefixMatch: true,
|
||||
label: 'Keywords',
|
||||
items: ['FROM'].map(wrapText)
|
||||
}];
|
||||
}
|
||||
|
||||
private _getAfterEventSuggestions(): SuggestionGroup[] {
|
||||
return [
|
||||
{
|
||||
skipFilter: true,
|
||||
label: 'Keywords',
|
||||
items: ['SINCE'].map(wrapText)
|
||||
.map((suggestion: any) => {
|
||||
suggestion.deleteBackwards = 0;
|
||||
return suggestion;
|
||||
})
|
||||
},
|
||||
{
|
||||
skipFilter: true,
|
||||
label: 'Macros',
|
||||
items: ['$__timeFilter'].map(wrapText)
|
||||
.map((suggestion: any) => {
|
||||
suggestion.deleteBackwards = 0;
|
||||
return suggestion;
|
||||
})
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// private _getAfterNumberSuggestions(): SuggestionGroup[] {
|
||||
// return [{
|
||||
// prefixMatch: true,
|
||||
// label: 'Duration',
|
||||
// items: DURATION
|
||||
// .map(d => `${d} AGO`)
|
||||
// .map(wrapText)
|
||||
// }];
|
||||
// }
|
||||
|
||||
private _getAfterAgoSuggestions(): SuggestionGroup[] {
|
||||
return [{
|
||||
prefixMatch: true,
|
||||
label: 'Keywords',
|
||||
items: ['TIMESERIES', 'COMPARE WITH', 'FACET'].map(wrapText)
|
||||
}];
|
||||
}
|
||||
|
||||
private _getKeywordSuggestions(): SuggestionGroup[] {
|
||||
return [{
|
||||
prefixMatch: true,
|
||||
label: 'Keywords',
|
||||
items: KEYWORDS.map(wrapText)
|
||||
}];
|
||||
}
|
||||
|
||||
private _getInitialSuggestions(): SuggestionGroup[] {
|
||||
// TODO: return datbase tables as an initial suggestion
|
||||
return [{
|
||||
prefixMatch: true,
|
||||
label: 'Keywords',
|
||||
items: KEYWORDS.map(wrapText)
|
||||
}];
|
||||
}
|
||||
|
||||
private async _fetchEvents() {
|
||||
const query = 'events';
|
||||
const result = await this.request(query);
|
||||
|
||||
if (result === undefined) {
|
||||
this.events = [];
|
||||
} else {
|
||||
this.events = result;
|
||||
}
|
||||
setTimeout(this.onTypeahead, 0);
|
||||
}
|
||||
|
||||
private async _fetchFields() {
|
||||
const query = 'fields';
|
||||
const result = await this.request(query);
|
||||
|
||||
this.fields = result || [];
|
||||
|
||||
setTimeout(this.onTypeahead, 0);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import KustoQueryField from './KustoQueryField';
|
||||
import Kusto from './kusto';
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import coreModule from 'app/core/core_module';
|
||||
|
||||
|
||||
|
||||
class Editor extends Component<any, any> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
edited: false,
|
||||
query: props.query || '',
|
||||
};
|
||||
}
|
||||
|
||||
onChangeQuery = value => {
|
||||
const { index, change } = this.props;
|
||||
const { query } = this.state;
|
||||
const edited = query !== value;
|
||||
this.setState({ edited, query: value });
|
||||
if (change) {
|
||||
change(value, index);
|
||||
}
|
||||
};
|
||||
|
||||
onPressEnter = () => {
|
||||
const { execute } = this.props;
|
||||
if (execute) {
|
||||
execute();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { request, variables } = this.props;
|
||||
const { edited, query } = this.state;
|
||||
|
||||
return (
|
||||
<div className="gf-form-input" style={{height: 'auto'}}>
|
||||
<KustoQueryField
|
||||
initialQuery={edited ? null : query}
|
||||
onPressEnter={this.onPressEnter}
|
||||
onQueryChange={this.onChangeQuery}
|
||||
prismLanguage="kusto"
|
||||
prismDefinition={Kusto}
|
||||
placeholder="Enter a query"
|
||||
request={request}
|
||||
templateVariables={variables}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.directive('kustoEditor', [
|
||||
'reactDirective',
|
||||
reactDirective => {
|
||||
return reactDirective(Editor, ['change', 'database', 'execute', 'query', 'request', 'variables']);
|
||||
},
|
||||
]);
|
@ -0,0 +1,88 @@
|
||||
export const FUNCTIONS = [
|
||||
{ text: 'countof', display: 'countof()', hint: '' },
|
||||
{ text: 'bin', display: 'bin()', hint: '' },
|
||||
{ text: 'extentid', display: 'extentid()', hint: '' },
|
||||
{ text: 'extract', display: 'extract()', hint: '' },
|
||||
{ text: 'extractjson', display: 'extractjson()', hint: '' },
|
||||
{ text: 'floor', display: 'floor()', hint: '' },
|
||||
{ text: 'iif', display: 'iif()', hint: '' },
|
||||
{ text: 'isnull', display: 'isnull()', hint: '' },
|
||||
{ text: 'isnotnull', display: 'isnotnull()', hint: '' },
|
||||
{ text: 'notnull', display: 'notnull()', hint: '' },
|
||||
{ text: 'isempty', display: 'isempty()', hint: '' },
|
||||
{ text: 'isnotempty', display: 'isnotempty()', hint: '' },
|
||||
{ text: 'notempty', display: 'notempty()', hint: '' },
|
||||
{ text: 'now', display: 'now()', hint: '' },
|
||||
{ text: 're2', display: 're2()', hint: '' },
|
||||
{ text: 'strcat', display: 'strcat()', hint: '' },
|
||||
{ text: 'strlen', display: 'strlen()', hint: '' },
|
||||
{ text: 'toupper', display: 'toupper()', hint: '' },
|
||||
{ text: 'tostring', display: 'tostring()', hint: '' },
|
||||
{ text: 'count', display: 'count()', hint: '' },
|
||||
{ text: 'cnt', display: 'cnt()', hint: '' },
|
||||
{ text: 'sum', display: 'sum()', hint: '' },
|
||||
{ text: 'min', display: 'min()', hint: '' },
|
||||
{ text: 'max', display: 'max()', hint: '' },
|
||||
{ text: 'avg', display: 'avg()', hint: '' },
|
||||
];
|
||||
|
||||
export const KEYWORDS = [
|
||||
'by', 'on', 'contains', 'notcontains', 'containscs', 'notcontainscs', 'startswith', 'has', 'matches', 'regex', 'true',
|
||||
'false', 'and', 'or', 'typeof', 'int', 'string', 'date', 'datetime', 'time', 'long', 'real', 'boolean', 'bool',
|
||||
// add some more keywords
|
||||
'where', 'order'
|
||||
];
|
||||
|
||||
// Kusto operators
|
||||
// export const OPERATORS = ['+', '-', '*', '/', '>', '<', '==', '<>', '<=', '>=', '~', '!~'];
|
||||
|
||||
export const DURATION = [
|
||||
'SECONDS',
|
||||
'MINUTES',
|
||||
'HOURS',
|
||||
'DAYS',
|
||||
'WEEKS',
|
||||
'MONTHS',
|
||||
'YEARS'
|
||||
];
|
||||
|
||||
const tokenizer = {
|
||||
comment: {
|
||||
pattern: /(^|[^\\:])\/\/.*/,
|
||||
lookbehind: true,
|
||||
greedy: true,
|
||||
},
|
||||
'function-context': {
|
||||
pattern: /[a-z0-9_]+\([^)]*\)?/i,
|
||||
inside: {},
|
||||
},
|
||||
duration: {
|
||||
pattern: new RegExp(`${DURATION.join('?|')}?`, 'i'),
|
||||
alias: 'number',
|
||||
},
|
||||
builtin: new RegExp(`\\b(?:${FUNCTIONS.map(f => f.text).join('|')})(?=\\s*\\()`, 'i'),
|
||||
string: {
|
||||
pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
|
||||
greedy: true,
|
||||
},
|
||||
keyword: new RegExp(`\\b(?:${KEYWORDS.join('|')}|\\*)\\b`, 'i'),
|
||||
boolean: /\b(?:true|false)\b/,
|
||||
number: /\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,
|
||||
operator: /-|\+|\*|\/|>|<|==|<=?|>=?|<>|!~|~|=|\|/,
|
||||
punctuation: /[{};(),.:]/,
|
||||
variable: /(\[\[(.+?)\]\])|(\$(.+?))\b/
|
||||
};
|
||||
|
||||
tokenizer['function-context'].inside = {
|
||||
argument: {
|
||||
pattern: /[a-z0-9_]+(?=:)/i,
|
||||
alias: 'symbol',
|
||||
},
|
||||
duration: tokenizer.duration,
|
||||
number: tokenizer.number,
|
||||
builtin: tokenizer.builtin,
|
||||
string: tokenizer.string,
|
||||
variable: tokenizer.variable,
|
||||
};
|
||||
|
||||
export default tokenizer;
|
@ -0,0 +1,333 @@
|
||||
import PluginPrism from './slate-plugins/prism';
|
||||
// import PluginPrism from 'slate-prism';
|
||||
// import Prism from 'prismjs';
|
||||
|
||||
import BracesPlugin from 'app/features/explore/slate-plugins/braces';
|
||||
import ClearPlugin from 'app/features/explore/slate-plugins/clear';
|
||||
// Custom plugins (new line on Enter and run on Shift+Enter)
|
||||
import NewlinePlugin from './slate-plugins/newline';
|
||||
import RunnerPlugin from './slate-plugins/runner';
|
||||
|
||||
import Typeahead from './typeahead';
|
||||
|
||||
import { Block, Document, Text, Value } from 'slate';
|
||||
import { Editor } from 'slate-react';
|
||||
import Plain from 'slate-plain-serializer';
|
||||
import ReactDOM from 'react-dom';
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
function flattenSuggestions(s) {
|
||||
return s ? s.reduce((acc, g) => acc.concat(g.items), []) : [];
|
||||
}
|
||||
|
||||
export const makeFragment = text => {
|
||||
const lines = text.split('\n').map(line =>
|
||||
Block.create({
|
||||
type: 'paragraph',
|
||||
nodes: [Text.create(line)],
|
||||
})
|
||||
);
|
||||
|
||||
const fragment = Document.create({
|
||||
nodes: lines,
|
||||
});
|
||||
return fragment;
|
||||
};
|
||||
|
||||
export const getInitialValue = query => Value.create({ document: makeFragment(query) });
|
||||
|
||||
class Portal extends React.Component<any, any> {
|
||||
node: any;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { index = 0, prefix = 'query' } = props;
|
||||
this.node = document.createElement('div');
|
||||
this.node.classList.add(`slate-typeahead`, `slate-typeahead-${prefix}-${index}`);
|
||||
document.body.appendChild(this.node);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.body.removeChild(this.node);
|
||||
}
|
||||
|
||||
render() {
|
||||
return ReactDOM.createPortal(this.props.children, this.node);
|
||||
}
|
||||
}
|
||||
|
||||
class QueryField extends React.Component<any, any> {
|
||||
menuEl: any;
|
||||
plugins: any;
|
||||
resetTimer: any;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
const { prismDefinition = {}, prismLanguage = 'kusto' } = props;
|
||||
|
||||
this.plugins = [
|
||||
BracesPlugin(),
|
||||
ClearPlugin(),
|
||||
RunnerPlugin({ handler: props.onPressEnter }),
|
||||
NewlinePlugin(),
|
||||
PluginPrism({ definition: prismDefinition, language: prismLanguage }),
|
||||
];
|
||||
|
||||
this.state = {
|
||||
labelKeys: {},
|
||||
labelValues: {},
|
||||
suggestions: [],
|
||||
typeaheadIndex: 0,
|
||||
typeaheadPrefix: '',
|
||||
value: getInitialValue(props.initialQuery || ''),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateMenu();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.resetTimer);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.updateMenu();
|
||||
}
|
||||
|
||||
onChange = ({ value }) => {
|
||||
const changed = value.document !== this.state.value.document;
|
||||
this.setState({ value }, () => {
|
||||
if (changed) {
|
||||
this.onChangeQuery();
|
||||
}
|
||||
});
|
||||
|
||||
window.requestAnimationFrame(this.onTypeahead);
|
||||
}
|
||||
|
||||
request = (url?) => {
|
||||
if (this.props.request) {
|
||||
return this.props.request(url);
|
||||
}
|
||||
return fetch(url);
|
||||
}
|
||||
|
||||
onChangeQuery = () => {
|
||||
// Send text change to parent
|
||||
const { onQueryChange } = this.props;
|
||||
if (onQueryChange) {
|
||||
onQueryChange(Plain.serialize(this.state.value));
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown = (event, change) => {
|
||||
const { typeaheadIndex, suggestions } = this.state;
|
||||
|
||||
switch (event.key) {
|
||||
case 'Escape': {
|
||||
if (this.menuEl) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.resetTypeahead();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ' ': {
|
||||
if (event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
this.onTypeahead();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'Tab': {
|
||||
if (this.menuEl) {
|
||||
// Dont blur input
|
||||
event.preventDefault();
|
||||
if (!suggestions || suggestions.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Get the currently selected suggestion
|
||||
const flattenedSuggestions = flattenSuggestions(suggestions);
|
||||
const selected = Math.abs(typeaheadIndex);
|
||||
const selectedIndex = selected % flattenedSuggestions.length || 0;
|
||||
const suggestion = flattenedSuggestions[selectedIndex];
|
||||
|
||||
this.applyTypeahead(change, suggestion);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ArrowDown': {
|
||||
if (this.menuEl) {
|
||||
// Select next suggestion
|
||||
event.preventDefault();
|
||||
this.setState({ typeaheadIndex: typeaheadIndex + 1 });
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ArrowUp': {
|
||||
if (this.menuEl) {
|
||||
// Select previous suggestion
|
||||
event.preventDefault();
|
||||
this.setState({ typeaheadIndex: Math.max(0, typeaheadIndex - 1) });
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// console.log('default key', event.key, event.which, event.charCode, event.locale, data.key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
onTypeahead = (change?, item?) => {
|
||||
return change || this.state.value.change();
|
||||
}
|
||||
|
||||
applyTypeahead(change?, suggestion?): { value: object } { return { value: {} }; }
|
||||
|
||||
resetTypeahead = () => {
|
||||
this.setState({
|
||||
suggestions: [],
|
||||
typeaheadIndex: 0,
|
||||
typeaheadPrefix: '',
|
||||
typeaheadContext: null,
|
||||
});
|
||||
}
|
||||
|
||||
handleBlur = () => {
|
||||
const { onBlur } = this.props;
|
||||
// If we dont wait here, menu clicks wont work because the menu
|
||||
// will be gone.
|
||||
this.resetTimer = setTimeout(this.resetTypeahead, 100);
|
||||
if (onBlur) {
|
||||
onBlur();
|
||||
}
|
||||
}
|
||||
|
||||
handleFocus = () => {
|
||||
const { onFocus } = this.props;
|
||||
if (onFocus) {
|
||||
onFocus();
|
||||
}
|
||||
}
|
||||
|
||||
onClickItem = item => {
|
||||
const { suggestions } = this.state;
|
||||
if (!suggestions || suggestions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the currently selected suggestion
|
||||
const flattenedSuggestions = flattenSuggestions(suggestions);
|
||||
const suggestion = _.find(
|
||||
flattenedSuggestions,
|
||||
suggestion => suggestion.display === item || suggestion.text === item
|
||||
);
|
||||
|
||||
// Manually triggering change
|
||||
const change = this.applyTypeahead(this.state.value.change(), suggestion);
|
||||
this.onChange(change);
|
||||
}
|
||||
|
||||
updateMenu = () => {
|
||||
const { suggestions } = this.state;
|
||||
const menu = this.menuEl;
|
||||
const selection = window.getSelection();
|
||||
const node = selection.anchorNode;
|
||||
|
||||
// No menu, nothing to do
|
||||
if (!menu) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No suggestions or blur, remove menu
|
||||
const hasSuggesstions = suggestions && suggestions.length > 0;
|
||||
if (!hasSuggesstions) {
|
||||
menu.removeAttribute('style');
|
||||
return;
|
||||
}
|
||||
|
||||
// Align menu overlay to editor node
|
||||
if (node && node.parentElement) {
|
||||
// Read from DOM
|
||||
const rect = node.parentElement.getBoundingClientRect();
|
||||
const scrollX = window.scrollX;
|
||||
const scrollY = window.scrollY;
|
||||
|
||||
// Write DOM
|
||||
requestAnimationFrame(() => {
|
||||
menu.style.opacity = 1;
|
||||
menu.style.top = `${rect.top + scrollY + rect.height + 4}px`;
|
||||
menu.style.left = `${rect.left + scrollX - 2}px`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
menuRef = el => {
|
||||
this.menuEl = el;
|
||||
}
|
||||
|
||||
renderMenu = () => {
|
||||
const { portalPrefix } = this.props;
|
||||
const { suggestions } = this.state;
|
||||
const hasSuggesstions = suggestions && suggestions.length > 0;
|
||||
if (!hasSuggesstions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Guard selectedIndex to be within the length of the suggestions
|
||||
let selectedIndex = Math.max(this.state.typeaheadIndex, 0);
|
||||
const flattenedSuggestions = flattenSuggestions(suggestions);
|
||||
selectedIndex = selectedIndex % flattenedSuggestions.length || 0;
|
||||
const selectedKeys = (flattenedSuggestions.length > 0 ? [flattenedSuggestions[selectedIndex]] : []).map(
|
||||
i => (typeof i === 'object' ? i.text : i)
|
||||
);
|
||||
|
||||
// Create typeahead in DOM root so we can later position it absolutely
|
||||
return (
|
||||
<Portal prefix={portalPrefix}>
|
||||
<Typeahead
|
||||
menuRef={this.menuRef}
|
||||
selectedItems={selectedKeys}
|
||||
onClickItem={this.onClickItem}
|
||||
groupedItems={suggestions}
|
||||
/>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="slate-query-field">
|
||||
{this.renderMenu()}
|
||||
<Editor
|
||||
autoCorrect={false}
|
||||
onBlur={this.handleBlur}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onChange={this.onChange}
|
||||
onFocus={this.handleFocus}
|
||||
placeholder={this.props.placeholder}
|
||||
plugins={this.plugins}
|
||||
spellCheck={false}
|
||||
value={this.state.value}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default QueryField;
|
@ -0,0 +1,35 @@
|
||||
function getIndent(text) {
|
||||
let offset = text.length - text.trimLeft().length;
|
||||
if (offset) {
|
||||
let indent = text[0];
|
||||
while (--offset) {
|
||||
indent += text[0];
|
||||
}
|
||||
return indent;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export default function NewlinePlugin() {
|
||||
return {
|
||||
onKeyDown(event, change) {
|
||||
const { value } = change;
|
||||
if (!value.isCollapsed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
|
||||
const { startBlock } = value;
|
||||
const currentLineText = startBlock.text;
|
||||
const indent = getIndent(currentLineText);
|
||||
|
||||
return change
|
||||
.splitBlock()
|
||||
.insertText(indent)
|
||||
.focus();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
import React from 'react';
|
||||
import Prism from 'prismjs';
|
||||
|
||||
const TOKEN_MARK = 'prism-token';
|
||||
|
||||
export function setPrismTokens(language, field, values, alias = 'variable') {
|
||||
Prism.languages[language][field] = {
|
||||
alias,
|
||||
pattern: new RegExp(`(?:^|\\s)(${values.join('|')})(?:$|\\s)`),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Code-highlighting plugin based on Prism and
|
||||
* https://github.com/ianstormtaylor/slate/blob/master/examples/code-highlighting/index.js
|
||||
*
|
||||
* (Adapted to handle nested grammar definitions.)
|
||||
*/
|
||||
|
||||
export default function PrismPlugin({ definition, language }) {
|
||||
if (definition) {
|
||||
// Don't override exising modified definitions
|
||||
Prism.languages[language] = Prism.languages[language] || definition;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Render a Slate mark with appropiate CSS class names
|
||||
*
|
||||
* @param {Object} props
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderMark(props) {
|
||||
const { children, mark } = props;
|
||||
// Only apply spans to marks identified by this plugin
|
||||
if (mark.type !== TOKEN_MARK) {
|
||||
return undefined;
|
||||
}
|
||||
const className = `token ${mark.data.get('types')}`;
|
||||
return <span className={className}>{children}</span>;
|
||||
},
|
||||
|
||||
/**
|
||||
* Decorate code blocks with Prism.js highlighting.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
decorateNode(node) {
|
||||
if (node.type !== 'paragraph') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const texts = node.getTexts().toArray();
|
||||
const tstring = texts.map(t => t.text).join('\n');
|
||||
const grammar = Prism.languages[language];
|
||||
const tokens = Prism.tokenize(tstring, grammar);
|
||||
const decorations: any[] = [];
|
||||
let startText = texts.shift();
|
||||
let endText = startText;
|
||||
let startOffset = 0;
|
||||
let endOffset = 0;
|
||||
let start = 0;
|
||||
|
||||
function processToken(token, acc?) {
|
||||
// Accumulate token types down the tree
|
||||
const types = `${acc || ''} ${token.type || ''} ${token.alias || ''}`;
|
||||
|
||||
// Add mark for token node
|
||||
if (typeof token === 'string' || typeof token.content === 'string') {
|
||||
startText = endText;
|
||||
startOffset = endOffset;
|
||||
|
||||
const content = typeof token === 'string' ? token : token.content;
|
||||
const newlines = content.split('\n').length - 1;
|
||||
const length = content.length - newlines;
|
||||
const end = start + length;
|
||||
|
||||
let available = startText.text.length - startOffset;
|
||||
let remaining = length;
|
||||
|
||||
endOffset = startOffset + remaining;
|
||||
|
||||
while (available < remaining) {
|
||||
endText = texts.shift();
|
||||
remaining = length - available;
|
||||
available = endText.text.length;
|
||||
endOffset = remaining;
|
||||
}
|
||||
|
||||
// Inject marks from up the tree (acc) as well
|
||||
if (typeof token !== 'string' || acc) {
|
||||
const range = {
|
||||
anchorKey: startText.key,
|
||||
anchorOffset: startOffset,
|
||||
focusKey: endText.key,
|
||||
focusOffset: endOffset,
|
||||
marks: [{ type: TOKEN_MARK, data: { types } }],
|
||||
};
|
||||
|
||||
decorations.push(range);
|
||||
}
|
||||
|
||||
start = end;
|
||||
} else if (token.content && token.content.length) {
|
||||
// Tokens can be nested
|
||||
for (const subToken of token.content) {
|
||||
processToken(subToken, types);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process top-level tokens
|
||||
for (const token of tokens) {
|
||||
processToken(token);
|
||||
}
|
||||
|
||||
return decorations;
|
||||
},
|
||||
};
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
export default function RunnerPlugin({ handler }) {
|
||||
return {
|
||||
onKeyDown(event) {
|
||||
// Handle enter
|
||||
if (handler && event.key === 'Enter' && event.shiftKey) {
|
||||
// Submit on Enter
|
||||
event.preventDefault();
|
||||
handler(event);
|
||||
return true;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
|
||||
function scrollIntoView(el) {
|
||||
if (!el || !el.offsetParent) {
|
||||
return;
|
||||
}
|
||||
const container = el.offsetParent;
|
||||
if (el.offsetTop > container.scrollTop + container.offsetHeight || el.offsetTop < container.scrollTop) {
|
||||
container.scrollTop = el.offsetTop - container.offsetTop;
|
||||
}
|
||||
}
|
||||
|
||||
class TypeaheadItem extends React.PureComponent<any, any> {
|
||||
el: any;
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.isSelected && !prevProps.isSelected) {
|
||||
scrollIntoView(this.el);
|
||||
}
|
||||
}
|
||||
|
||||
getRef = el => {
|
||||
this.el = el;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { hint, isSelected, label, onClickItem } = this.props;
|
||||
const className = isSelected ? 'typeahead-item typeahead-item__selected' : 'typeahead-item';
|
||||
const onClick = () => onClickItem(label);
|
||||
return (
|
||||
<li ref={this.getRef} className={className} onClick={onClick}>
|
||||
{label}
|
||||
{hint && isSelected ? <div className="typeahead-item-hint">{hint}</div> : null}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TypeaheadGroup extends React.PureComponent<any, any> {
|
||||
render() {
|
||||
const { items, label, selected, onClickItem } = this.props;
|
||||
return (
|
||||
<li className="typeahead-group">
|
||||
<div className="typeahead-group__title">{label}</div>
|
||||
<ul className="typeahead-group__list">
|
||||
{items.map(item => {
|
||||
const text = typeof item === 'object' ? item.text : item;
|
||||
const label = typeof item === 'object' ? item.display || item.text : item;
|
||||
return (
|
||||
<TypeaheadItem
|
||||
key={text}
|
||||
onClickItem={onClickItem}
|
||||
isSelected={selected.indexOf(text) > -1}
|
||||
hint={item.hint}
|
||||
label={label}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Typeahead extends React.PureComponent<any, any> {
|
||||
render() {
|
||||
const { groupedItems, menuRef, selectedItems, onClickItem } = this.props;
|
||||
return (
|
||||
<ul className="typeahead" ref={menuRef}>
|
||||
{groupedItems.map(g => (
|
||||
<TypeaheadGroup key={g.label} onClickItem={onClickItem} selected={selectedItems} {...g} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Typeahead;
|
@ -1 +0,0 @@
|
||||
Object.assign({});
|
@ -1,219 +0,0 @@
|
||||
// tslint:disable-next-line:no-reference
|
||||
///<reference path="../../../../../../node_modules/monaco-editor/monaco.d.ts" />
|
||||
|
||||
import KustoCodeEditor from './kusto_code_editor';
|
||||
import _ from 'lodash';
|
||||
|
||||
describe('KustoCodeEditor', () => {
|
||||
let editor;
|
||||
|
||||
describe('getCompletionItems', () => {
|
||||
let completionItems;
|
||||
let lineContent;
|
||||
let model;
|
||||
|
||||
beforeEach(() => {
|
||||
(global as any).monaco = {
|
||||
languages: {
|
||||
CompletionItemKind: {
|
||||
Keyword: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
model = {
|
||||
getLineCount: () => 3,
|
||||
getValueInRange: () => 'atable/n' + lineContent,
|
||||
getLineContent: () => lineContent,
|
||||
};
|
||||
|
||||
const StandaloneMock = jest.fn<monaco.editor.ICodeEditor>();
|
||||
editor = new KustoCodeEditor(null, 'TimeGenerated', () => {}, {});
|
||||
editor.codeEditor = new StandaloneMock();
|
||||
});
|
||||
|
||||
describe('when no where clause and no | in model text', () => {
|
||||
beforeEach(() => {
|
||||
lineContent = ' ';
|
||||
const position = { lineNumber: 2, column: 2 };
|
||||
completionItems = editor.getCompletionItems(model, position);
|
||||
});
|
||||
|
||||
it('should not return any grafana macros', () => {
|
||||
expect(completionItems.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when no where clause in model text', () => {
|
||||
beforeEach(() => {
|
||||
lineContent = '| ';
|
||||
const position = { lineNumber: 2, column: 3 };
|
||||
completionItems = editor.getCompletionItems(model, position);
|
||||
});
|
||||
|
||||
it('should return grafana macros for where and timefilter', () => {
|
||||
expect(completionItems.length).toBe(1);
|
||||
|
||||
expect(completionItems[0].label).toBe('where $__timeFilter(timeColumn)');
|
||||
expect(completionItems[0].insertText.value).toBe('where \\$__timeFilter(${0:TimeGenerated})');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when on line with where clause', () => {
|
||||
beforeEach(() => {
|
||||
lineContent = '| where Test == 2 and ';
|
||||
const position = { lineNumber: 2, column: 23 };
|
||||
completionItems = editor.getCompletionItems(model, position);
|
||||
});
|
||||
|
||||
it('should return grafana macros and variables', () => {
|
||||
expect(completionItems.length).toBe(4);
|
||||
|
||||
expect(completionItems[0].label).toBe('$__timeFilter(timeColumn)');
|
||||
expect(completionItems[0].insertText.value).toBe('\\$__timeFilter(${0:TimeGenerated})');
|
||||
|
||||
expect(completionItems[1].label).toBe('$__from');
|
||||
expect(completionItems[1].insertText.value).toBe('\\$__from');
|
||||
|
||||
expect(completionItems[2].label).toBe('$__to');
|
||||
expect(completionItems[2].insertText.value).toBe('\\$__to');
|
||||
|
||||
expect(completionItems[3].label).toBe('$__interval');
|
||||
expect(completionItems[3].insertText.value).toBe('\\$__interval');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('onDidChangeCursorSelection', () => {
|
||||
const keyboardEvent = {
|
||||
selection: {
|
||||
startLineNumber: 4,
|
||||
startColumn: 26,
|
||||
endLineNumber: 4,
|
||||
endColumn: 31,
|
||||
selectionStartLineNumber: 4,
|
||||
selectionStartColumn: 26,
|
||||
positionLineNumber: 4,
|
||||
positionColumn: 31,
|
||||
},
|
||||
secondarySelections: [],
|
||||
source: 'keyboard',
|
||||
reason: 3,
|
||||
};
|
||||
|
||||
const modelChangedEvent = {
|
||||
selection: {
|
||||
startLineNumber: 2,
|
||||
startColumn: 1,
|
||||
endLineNumber: 3,
|
||||
endColumn: 3,
|
||||
selectionStartLineNumber: 2,
|
||||
selectionStartColumn: 1,
|
||||
positionLineNumber: 3,
|
||||
positionColumn: 3,
|
||||
},
|
||||
secondarySelections: [],
|
||||
source: 'modelChange',
|
||||
reason: 2,
|
||||
};
|
||||
|
||||
describe('suggestion trigger', () => {
|
||||
let suggestionTriggered;
|
||||
let lineContent = '';
|
||||
|
||||
beforeEach(() => {
|
||||
(global as any).monaco = {
|
||||
languages: {
|
||||
CompletionItemKind: {
|
||||
Keyword: '',
|
||||
},
|
||||
},
|
||||
editor: {
|
||||
CursorChangeReason: {
|
||||
NotSet: 0,
|
||||
ContentFlush: 1,
|
||||
RecoverFromMarkers: 2,
|
||||
Explicit: 3,
|
||||
Paste: 4,
|
||||
Undo: 5,
|
||||
Redo: 6,
|
||||
},
|
||||
},
|
||||
};
|
||||
const StandaloneMock = jest.fn<monaco.editor.ICodeEditor>(() => ({
|
||||
getModel: () => {
|
||||
return {
|
||||
getLineCount: () => 3,
|
||||
getLineContent: () => lineContent,
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
editor = new KustoCodeEditor(null, 'TimeGenerated', () => {}, {});
|
||||
editor.codeEditor = new StandaloneMock();
|
||||
editor.triggerSuggestions = () => {
|
||||
suggestionTriggered = true;
|
||||
};
|
||||
});
|
||||
|
||||
describe('when model change event, reason is RecoverFromMarkers and there is a space after', () => {
|
||||
beforeEach(() => {
|
||||
suggestionTriggered = false;
|
||||
lineContent = '| ';
|
||||
editor.onDidChangeCursorSelection(modelChangedEvent);
|
||||
});
|
||||
|
||||
it('should trigger suggestion', () => {
|
||||
expect(suggestionTriggered).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when not model change event', () => {
|
||||
beforeEach(() => {
|
||||
suggestionTriggered = false;
|
||||
editor.onDidChangeCursorSelection(keyboardEvent);
|
||||
});
|
||||
|
||||
it('should not trigger suggestion', () => {
|
||||
expect(suggestionTriggered).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when model change event but with incorrect reason', () => {
|
||||
beforeEach(() => {
|
||||
suggestionTriggered = false;
|
||||
const modelChangedWithInvalidReason = _.cloneDeep(modelChangedEvent);
|
||||
modelChangedWithInvalidReason.reason = 5;
|
||||
editor.onDidChangeCursorSelection(modelChangedWithInvalidReason);
|
||||
});
|
||||
|
||||
it('should not trigger suggestion', () => {
|
||||
expect(suggestionTriggered).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when model change event but with no space after', () => {
|
||||
beforeEach(() => {
|
||||
suggestionTriggered = false;
|
||||
lineContent = '|';
|
||||
editor.onDidChangeCursorSelection(modelChangedEvent);
|
||||
});
|
||||
|
||||
it('should not trigger suggestion', () => {
|
||||
expect(suggestionTriggered).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when model change event but with no space after', () => {
|
||||
beforeEach(() => {
|
||||
suggestionTriggered = false;
|
||||
lineContent = '|';
|
||||
editor.onDidChangeCursorSelection(modelChangedEvent);
|
||||
});
|
||||
|
||||
it('should not trigger suggestion', () => {
|
||||
expect(suggestionTriggered).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,332 +0,0 @@
|
||||
// tslint:disable-next-line:no-reference
|
||||
///<reference path="../../../../../../node_modules/monaco-editor/monaco.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
export interface SuggestionController {
|
||||
_model: any;
|
||||
}
|
||||
|
||||
export default class KustoCodeEditor {
|
||||
codeEditor: monaco.editor.IStandaloneCodeEditor;
|
||||
completionItemProvider: monaco.IDisposable;
|
||||
signatureHelpProvider: monaco.IDisposable;
|
||||
|
||||
splitWithNewLineRegex = /[^\n]+\n?|\n/g;
|
||||
newLineRegex = /\r?\n/;
|
||||
startsWithKustoPipeRegex = /^\|\s*/g;
|
||||
kustoPipeRegexStrict = /^\|\s*$/g;
|
||||
|
||||
constructor(
|
||||
private containerDiv: any,
|
||||
private defaultTimeField: string,
|
||||
private getSchema: () => any,
|
||||
private config: any
|
||||
) {}
|
||||
|
||||
initMonaco(scope) {
|
||||
const themeName = this.config.bootData.user.lightTheme ? 'grafana-light' : 'vs-dark';
|
||||
|
||||
monaco.editor.defineTheme('grafana-light', {
|
||||
base: 'vs',
|
||||
inherit: true,
|
||||
rules: [
|
||||
{ token: 'comment', foreground: '008000' },
|
||||
{ token: 'variable.predefined', foreground: '800080' },
|
||||
{ token: 'function', foreground: '0000FF' },
|
||||
{ token: 'operator.sql', foreground: 'FF4500' },
|
||||
{ token: 'string', foreground: 'B22222' },
|
||||
{ token: 'operator.scss', foreground: '0000FF' },
|
||||
{ token: 'variable', foreground: 'C71585' },
|
||||
{ token: 'variable.parameter', foreground: '9932CC' },
|
||||
{ token: '', foreground: '000000' },
|
||||
{ token: 'type', foreground: '0000FF' },
|
||||
{ token: 'tag', foreground: '0000FF' },
|
||||
{ token: 'annotation', foreground: '2B91AF' },
|
||||
{ token: 'keyword', foreground: '0000FF' },
|
||||
{ token: 'number', foreground: '191970' },
|
||||
{ token: 'annotation', foreground: '9400D3' },
|
||||
{ token: 'invalid', background: 'cd3131' },
|
||||
],
|
||||
colors: {
|
||||
'textCodeBlock.background': '#FFFFFF',
|
||||
},
|
||||
});
|
||||
|
||||
monaco.languages['kusto'].kustoDefaults.setLanguageSettings({
|
||||
includeControlCommands: true,
|
||||
newlineAfterPipe: true,
|
||||
useIntellisenseV2: false,
|
||||
useSemanticColorization: true,
|
||||
});
|
||||
|
||||
this.codeEditor = monaco.editor.create(this.containerDiv, {
|
||||
value: scope.content || 'Write your query here',
|
||||
language: 'kusto',
|
||||
// language: 'go',
|
||||
selectionHighlight: false,
|
||||
theme: themeName,
|
||||
folding: true,
|
||||
lineNumbers: 'off',
|
||||
lineHeight: 16,
|
||||
suggestFontSize: 13,
|
||||
dragAndDrop: false,
|
||||
occurrencesHighlight: false,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
renderIndentGuides: false,
|
||||
wordWrap: 'on',
|
||||
});
|
||||
this.codeEditor.layout();
|
||||
|
||||
if (monaco.editor.getModels().length === 1) {
|
||||
this.completionItemProvider = monaco.languages.registerCompletionItemProvider('kusto', {
|
||||
triggerCharacters: ['.', ' '],
|
||||
provideCompletionItems: this.getCompletionItems.bind(this),
|
||||
});
|
||||
|
||||
this.signatureHelpProvider = monaco.languages.registerSignatureHelpProvider('kusto', {
|
||||
signatureHelpTriggerCharacters: ['(', ')'],
|
||||
provideSignatureHelp: this.getSignatureHelp.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
this.codeEditor.createContextKey('readyToExecute', true);
|
||||
|
||||
this.codeEditor.onDidChangeCursorSelection(event => {
|
||||
this.onDidChangeCursorSelection(event);
|
||||
});
|
||||
|
||||
this.getSchema().then(schema => {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
|
||||
monaco.languages['kusto'].getKustoWorker().then(workerAccessor => {
|
||||
const model = this.codeEditor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
workerAccessor(model.uri).then(worker => {
|
||||
const dbName = Object.keys(schema.Databases).length > 0 ? Object.keys(schema.Databases)[0] : '';
|
||||
worker.setSchemaFromShowSchema(schema, 'https://help.kusto.windows.net', dbName);
|
||||
this.codeEditor.layout();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setOnDidChangeModelContent(listener) {
|
||||
this.codeEditor.onDidChangeModelContent(listener);
|
||||
}
|
||||
|
||||
disposeMonaco() {
|
||||
if (this.completionItemProvider) {
|
||||
try {
|
||||
this.completionItemProvider.dispose();
|
||||
} catch (e) {
|
||||
console.error('Failed to dispose the completion item provider.', e);
|
||||
}
|
||||
}
|
||||
if (this.signatureHelpProvider) {
|
||||
try {
|
||||
this.signatureHelpProvider.dispose();
|
||||
} catch (e) {
|
||||
console.error('Failed to dispose the signature help provider.', e);
|
||||
}
|
||||
}
|
||||
if (this.codeEditor) {
|
||||
try {
|
||||
this.codeEditor.dispose();
|
||||
} catch (e) {
|
||||
console.error('Failed to dispose the editor component.', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addCommand(keybinding: number, commandFunc: monaco.editor.ICommandHandler) {
|
||||
this.codeEditor.addCommand(keybinding, commandFunc, 'readyToExecute');
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.codeEditor.getValue();
|
||||
}
|
||||
|
||||
toSuggestionController(srv: monaco.editor.IEditorContribution): SuggestionController {
|
||||
return srv as any;
|
||||
}
|
||||
|
||||
setEditorContent(value) {
|
||||
this.codeEditor.setValue(value);
|
||||
}
|
||||
|
||||
getCompletionItems(model: monaco.editor.IReadOnlyModel, position: monaco.Position) {
|
||||
const timeFilterDocs =
|
||||
'##### Macro that uses the selected timerange in Grafana to filter the query.\n\n' +
|
||||
'- `$__timeFilter()` -> Uses the ' +
|
||||
this.defaultTimeField +
|
||||
' column\n\n' +
|
||||
'- `$__timeFilter(datetimeColumn)` -> Uses the specified datetime column to build the query.';
|
||||
|
||||
const textUntilPosition = model.getValueInRange({
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: position.lineNumber,
|
||||
endColumn: position.column,
|
||||
});
|
||||
|
||||
if (!_.includes(textUntilPosition, '|')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!_.includes(textUntilPosition.toLowerCase(), 'where')) {
|
||||
return [
|
||||
{
|
||||
label: 'where $__timeFilter(timeColumn)',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: {
|
||||
value: 'where \\$__timeFilter(${0:' + this.defaultTimeField + '})',
|
||||
},
|
||||
documentation: {
|
||||
value: timeFilterDocs,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (_.includes(model.getLineContent(position.lineNumber).toLowerCase(), 'where')) {
|
||||
return [
|
||||
{
|
||||
label: '$__timeFilter(timeColumn)',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: {
|
||||
value: '\\$__timeFilter(${0:' + this.defaultTimeField + '})',
|
||||
},
|
||||
documentation: {
|
||||
value: timeFilterDocs,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '$__from',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: {
|
||||
value: `\\$__from`,
|
||||
},
|
||||
documentation: {
|
||||
value:
|
||||
'Built-in variable that returns the from value of the selected timerange in Grafana.\n\n' +
|
||||
'Example: `where ' +
|
||||
this.defaultTimeField +
|
||||
' > $__from` ',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '$__to',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: {
|
||||
value: `\\$__to`,
|
||||
},
|
||||
documentation: {
|
||||
value:
|
||||
'Built-in variable that returns the to value of the selected timerange in Grafana.\n\n' +
|
||||
'Example: `where ' +
|
||||
this.defaultTimeField +
|
||||
' < $__to` ',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '$__interval',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: {
|
||||
value: `\\$__interval`,
|
||||
},
|
||||
documentation: {
|
||||
value:
|
||||
'##### Built-in variable that returns an automatic time grain suitable for the current timerange.\n\n' +
|
||||
'Used with the bin() function - `bin(' +
|
||||
this.defaultTimeField +
|
||||
', $__interval)` \n\n' +
|
||||
'[Grafana docs](http://docs.grafana.org/reference/templating/#the-interval-variable)',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
getSignatureHelp(model: monaco.editor.IReadOnlyModel, position: monaco.Position, token: monaco.CancellationToken) {
|
||||
const textUntilPosition = model.getValueInRange({
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn: position.column - 14,
|
||||
endLineNumber: position.lineNumber,
|
||||
endColumn: position.column,
|
||||
});
|
||||
|
||||
if (textUntilPosition !== '$__timeFilter(') {
|
||||
return {} as monaco.languages.SignatureHelp;
|
||||
}
|
||||
|
||||
const signature: monaco.languages.SignatureHelp = {
|
||||
activeParameter: 0,
|
||||
activeSignature: 0,
|
||||
signatures: [
|
||||
{
|
||||
label: '$__timeFilter(timeColumn)',
|
||||
parameters: [
|
||||
{
|
||||
label: 'timeColumn',
|
||||
documentation:
|
||||
'Default is ' +
|
||||
this.defaultTimeField +
|
||||
' column. Datetime column to filter data using the selected date range. ',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
onDidChangeCursorSelection(event) {
|
||||
if (event.source !== 'modelChange' || event.reason !== monaco.editor.CursorChangeReason.RecoverFromMarkers) {
|
||||
return;
|
||||
}
|
||||
const lastChar = this.getCharAt(event.selection.positionLineNumber, event.selection.positionColumn - 1);
|
||||
|
||||
if (lastChar !== ' ') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.triggerSuggestions();
|
||||
}
|
||||
|
||||
triggerSuggestions() {
|
||||
const suggestController = this.codeEditor.getContribution('editor.contrib.suggestController');
|
||||
if (!suggestController) {
|
||||
return;
|
||||
}
|
||||
|
||||
const convertedController = this.toSuggestionController(suggestController);
|
||||
|
||||
convertedController._model.cancel();
|
||||
setTimeout(() => {
|
||||
convertedController._model.trigger(true);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
getCharAt(lineNumber: number, column: number) {
|
||||
const model = this.codeEditor.getModel();
|
||||
if (model.getLineCount() === 0 || model.getLineCount() < lineNumber) {
|
||||
return '';
|
||||
}
|
||||
const line = model.getLineContent(lineNumber);
|
||||
if (line.length < column || column < 1) {
|
||||
return '';
|
||||
}
|
||||
return line[column - 1];
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
// tslint:disable-next-line:no-reference
|
||||
// ///<reference path="../../../../../../node_modules/monaco-editor/monaco.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
import KustoCodeEditor from './kusto_code_editor';
|
||||
import config from 'app/core/config';
|
||||
|
||||
/**
|
||||
* Load monaco code editor and its' dependencies as a separate webpack chunk.
|
||||
*/
|
||||
function importMonaco() {
|
||||
return import(
|
||||
/* webpackChunkName: "monaco" */
|
||||
'./monaco-loader'
|
||||
).then(monaco => {
|
||||
return monaco;
|
||||
}).catch(error => {
|
||||
console.error('An error occurred while loading monaco-kusto:\n', error);
|
||||
});
|
||||
}
|
||||
|
||||
const editorTemplate = `<div id="content" tabindex="0" style="width: 100%; height: 120px"></div>`;
|
||||
|
||||
function link(scope, elem, attrs) {
|
||||
const containerDiv = elem.find('#content')[0];
|
||||
|
||||
if (!(global as any).monaco) {
|
||||
// (global as any).System.import(`./${scope.pluginBaseUrl}/lib/monaco.min.js`).then(() => {
|
||||
importMonaco().then(() => {
|
||||
setTimeout(() => {
|
||||
initMonaco(containerDiv, scope);
|
||||
}, 1);
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
initMonaco(containerDiv, scope);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
containerDiv.onblur = () => {
|
||||
scope.onChange();
|
||||
};
|
||||
|
||||
containerDiv.onkeydown = evt => {
|
||||
if (evt.key === 'Escape') {
|
||||
evt.stopPropagation();
|
||||
return true;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
function initMonaco(containerDiv, scope) {
|
||||
const kustoCodeEditor = new KustoCodeEditor(containerDiv, scope.defaultTimeField, scope.getSchema, config);
|
||||
|
||||
kustoCodeEditor.initMonaco(scope);
|
||||
|
||||
/* tslint:disable:no-bitwise */
|
||||
kustoCodeEditor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Enter, () => {
|
||||
const newValue = kustoCodeEditor.getValue();
|
||||
scope.content = newValue;
|
||||
scope.onChange();
|
||||
});
|
||||
/* tslint:enable:no-bitwise */
|
||||
|
||||
// Sync with outer scope - update editor content if model has been changed from outside of directive.
|
||||
scope.$watch('content', (newValue, oldValue) => {
|
||||
const editorValue = kustoCodeEditor.getValue();
|
||||
if (newValue !== editorValue && newValue !== oldValue) {
|
||||
scope.$$postDigest(() => {
|
||||
kustoCodeEditor.setEditorContent(newValue);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
kustoCodeEditor.setOnDidChangeModelContent(() => {
|
||||
scope.$apply(() => {
|
||||
const newValue = kustoCodeEditor.getValue();
|
||||
scope.content = newValue;
|
||||
});
|
||||
});
|
||||
|
||||
scope.$on('$destroy', () => {
|
||||
kustoCodeEditor.disposeMonaco();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** @ngInject */
|
||||
export function kustoMonacoEditorDirective() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: editorTemplate,
|
||||
scope: {
|
||||
content: '=',
|
||||
onChange: '&',
|
||||
getSchema: '&',
|
||||
defaultTimeField: '@',
|
||||
pluginBaseUrl: '@',
|
||||
},
|
||||
link: link,
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('grafana.controllers').directive('kustoMonacoEditor', kustoMonacoEditorDirective);
|
@ -1,21 +0,0 @@
|
||||
// tslint:disable:no-reference
|
||||
// ///<reference path="../../../../../../node_modules/@alexanderzobnin/monaco-kusto/release/min/monaco.d.ts" />
|
||||
|
||||
// (1) Desired editor features:
|
||||
import "monaco-editor/esm/vs/editor/browser/controller/coreCommands.js";
|
||||
import 'monaco-editor/esm/vs/editor/browser/widget/codeEditorWidget.js';
|
||||
import 'monaco-editor/esm/vs/editor/contrib/contextmenu/contextmenu.js';
|
||||
import "monaco-editor/esm/vs/editor/contrib/find/findController.js";
|
||||
import 'monaco-editor/esm/vs/editor/contrib/folding/folding.js';
|
||||
import 'monaco-editor/esm/vs/editor/contrib/format/formatActions.js';
|
||||
import 'monaco-editor/esm/vs/editor/contrib/multicursor/multicursor.js';
|
||||
import 'monaco-editor/esm/vs/editor/contrib/suggest/suggestController.js';
|
||||
import 'monaco-editor/esm/vs/editor/contrib/wordHighlighter/wordHighlighter.js';
|
||||
import 'monaco-editor/esm/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.js';
|
||||
import "monaco-editor/esm/vs/editor/editor.api.js";
|
||||
|
||||
// (2) Desired languages:
|
||||
import '@alexanderzobnin/monaco-kusto/release/webpack/bridge.min.js';
|
||||
import '@alexanderzobnin/monaco-kusto/release/webpack/Kusto.JavaScript.Client.min.js';
|
||||
import '@alexanderzobnin/monaco-kusto/release/webpack/Kusto.Language.Bridge.min.js';
|
||||
import '@alexanderzobnin/monaco-kusto/release/webpack/monaco.contribution.min.js';
|
@ -118,8 +118,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<kusto-monaco-editor content="ctrl.target.azureLogAnalytics.query" on-change="ctrl.refresh()" get-schema="ctrl.getAzureLogAnalyticsSchema()"
|
||||
default-time-field="TimeGenerated" plugin-base-url={{ctrl.datasource.meta.baseUrl}}></kusto-monaco-editor>
|
||||
<!-- <kusto-monaco-editor content="ctrl.target.azureLogAnalytics.query" on-change="ctrl.refresh()" get-schema="ctrl.getAzureLogAnalyticsSchema()"
|
||||
default-time-field="TimeGenerated" plugin-base-url={{ctrl.datasource.meta.baseUrl}}></kusto-monaco-editor> -->
|
||||
|
||||
<div class="gf-form gf-form--grow">
|
||||
<kusto-editor
|
||||
class="gf-form gf-form--grow"
|
||||
request="ctrl.requestMetadata"
|
||||
style="border: none"
|
||||
query="ctrl.target.azureLogAnalytics.query"
|
||||
change="ctrl.onLogAnalyticsQueryChange"
|
||||
execute="ctrl.onLogAnalyticsQueryExecute"
|
||||
variables="ctrl.templateVariables"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
|
@ -2,7 +2,8 @@ import _ from 'lodash';
|
||||
import { QueryCtrl } from 'app/plugins/sdk';
|
||||
// import './css/query_editor.css';
|
||||
import TimegrainConverter from './time_grain_converter';
|
||||
import './monaco/kusto_monaco_editor';
|
||||
// import './monaco/kusto_monaco_editor';
|
||||
import './editor/editor_component';
|
||||
|
||||
export interface ResultFormat {
|
||||
text: string;
|
||||
@ -323,6 +324,18 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
||||
.catch(this.handleQueryCtrlError.bind(this));
|
||||
}
|
||||
|
||||
onLogAnalyticsQueryChange = (nextQuery: string) => {
|
||||
this.target.azureLogAnalytics.query = nextQuery;
|
||||
}
|
||||
|
||||
onLogAnalyticsQueryExecute = () => {
|
||||
this.panelCtrl.refresh();
|
||||
}
|
||||
|
||||
get templateVariables() {
|
||||
return this.templateSrv.variables.map(t => '$' + t.name);
|
||||
}
|
||||
|
||||
/* Application Insights Section */
|
||||
|
||||
getAppInsightsAutoInterval() {
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
const monaco = require('./webpack.monaco.js');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
@ -10,7 +9,7 @@ const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
|
||||
module.exports = merge(common, monaco, {
|
||||
module.exports = merge(common, {
|
||||
devtool: "cheap-module-source-map",
|
||||
mode: 'development',
|
||||
|
||||
|
@ -1,122 +0,0 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
// output: {
|
||||
// filename: 'monaco.min.js',
|
||||
// path: path.resolve(__dirname, 'dist'),
|
||||
// libraryTarget: 'umd',
|
||||
// library: 'monaco',
|
||||
// globalObject: 'self'
|
||||
// },
|
||||
entry: {
|
||||
// monaco: './public/app/plugins/datasource/grafana-azure-monitor-datasource/monaco/monaco-loader.ts',
|
||||
},
|
||||
output: {
|
||||
// filename: 'monaco.min.js',
|
||||
// chunkFilename: '[name].bundle.js',
|
||||
globalObject: 'self',
|
||||
},
|
||||
resolveLoader: {
|
||||
alias: {
|
||||
'blob-url-loader': require.resolve('./loaders/blobUrl'),
|
||||
'compile-loader': require.resolve('./loaders/compile'),
|
||||
},
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [ 'style-loader', 'css-loader' ]
|
||||
},
|
||||
// {
|
||||
// // https://github.com/bridgedotnet/Bridge/issues/3097
|
||||
// test: /bridge\.js$/,
|
||||
// loader: 'regexp-replace-loader',
|
||||
// options: {
|
||||
// match: {
|
||||
// pattern: "globals\\.System\\s=\\s\\{\\};"
|
||||
// },
|
||||
// replaceWith: "$& System = globals.System; "
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// test: /Kusto\.JavaScript\.Client\.js$/,
|
||||
// loader: 'regexp-replace-loader',
|
||||
// options: {
|
||||
// match: {
|
||||
// pattern: '"use strict";'
|
||||
// },
|
||||
// replaceWith: "$& System = globals.System; "
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// test: /Kusto\.Language\.Bridge\.js$/,
|
||||
// loader: 'regexp-replace-loader',
|
||||
// options: {
|
||||
// match: {
|
||||
// pattern: '"use strict";'
|
||||
// },
|
||||
// replaceWith: "$& System = globals.System; "
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// test: /newtonsoft\.json\.js$/,
|
||||
// loader: 'regexp-replace-loader',
|
||||
// options: {
|
||||
// match: {
|
||||
// pattern: '"use strict";'
|
||||
// },
|
||||
// replaceWith: "$& System = globals.System; "
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// test: /monaco\.contribution\.js$/,
|
||||
// loader: 'regexp-replace-loader',
|
||||
// options: {
|
||||
// match: {
|
||||
// pattern: 'vs/language/kusto/kustoMode',
|
||||
// flags: 'g'
|
||||
// },
|
||||
// replaceWith: "./kustoMode"
|
||||
// }
|
||||
// },
|
||||
]
|
||||
},
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
// chunks: 'all',
|
||||
cacheGroups: {
|
||||
// monacoContribution: {
|
||||
// test: /(src)|(node_modules(?!\/@kusto))/,
|
||||
// name: 'monaco.contribution',
|
||||
// enforce: false,
|
||||
// // chunks: 'all',
|
||||
// },
|
||||
// bridge: {
|
||||
// test: /bridge/,
|
||||
// name: 'bridge',
|
||||
// chunks: 'all',
|
||||
// },
|
||||
// KustoJavaScriptClient: {
|
||||
// test: /Kusto\.JavaScript\.Client/,
|
||||
// name: 'kusto.javaScript.client',
|
||||
// chunks: 'all',
|
||||
// },
|
||||
// KustoLanguageBridge: {
|
||||
// test: /Kusto\.Language\.Bridge/,
|
||||
// name: 'kusto.language.bridge',
|
||||
// chunks: 'all',
|
||||
// },
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new webpack.IgnorePlugin(/^((fs)|(path)|(os)|(crypto)|(source-map-support))$/, /vs\/language\/typescript\/lib/),
|
||||
// new webpack.optimize.LimitChunkCountPlugin({
|
||||
// maxChunks: 1,
|
||||
// }),
|
||||
// new UglifyJSPlugin()
|
||||
],
|
||||
};
|
23
yarn.lock
23
yarn.lock
@ -2,14 +2,6 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@alexanderzobnin/monaco-kusto@^0.2.3-rc.1":
|
||||
version "0.2.3-rc.1"
|
||||
resolved "https://registry.yarnpkg.com/@alexanderzobnin/monaco-kusto/-/monaco-kusto-0.2.3-rc.1.tgz#1e96eef584d6173f19670afb3f122947329a840f"
|
||||
integrity sha512-bppmiGfH7iXL4AdaKV2No4WqUoWBvS4bDejSYO0VjaK8zrNPFz9QhmGhnASHvQdmexcoPEqqQScA3udA2bQ68A==
|
||||
dependencies:
|
||||
"@kusto/language-service" "0.0.22-alpha"
|
||||
"@kusto/language-service-next" "0.0.25-alpha1"
|
||||
|
||||
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.0.0-beta.35":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
|
||||
@ -662,16 +654,6 @@
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.8.2.tgz#576ff7fb1230185b619a75d258cbc98f0867a8dc"
|
||||
|
||||
"@kusto/language-service-next@0.0.25-alpha1":
|
||||
version "0.0.25-alpha1"
|
||||
resolved "https://registry.yarnpkg.com/@kusto/language-service-next/-/language-service-next-0.0.25-alpha1.tgz#73977b0873c7c2a23ae0c2cc1fef95a68c723c09"
|
||||
integrity sha512-xxdY+Ei+e/GuzWZYoyjQqOfuzwVPMfHJwPRcxOdcSq5XMt9oZS+ryVH66l+CBxdZDdxEfQD2evVTXLjOAck5Rg==
|
||||
|
||||
"@kusto/language-service@0.0.22-alpha":
|
||||
version "0.0.22-alpha"
|
||||
resolved "https://registry.yarnpkg.com/@kusto/language-service/-/language-service-0.0.22-alpha.tgz#990bbfb82e8e8991c35a12aab00d890a05fff623"
|
||||
integrity sha512-oYiakH2Lq4j7ghahAtqxC+nuOKybH03H1o3IWyB3p8Ll4WkYQOrV8GWpqEjPtMfsuOt3t5k55OzzwDWFaX2zlw==
|
||||
|
||||
"@mrmlnc/readdir-enhanced@^2.2.1":
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
|
||||
@ -12979,11 +12961,6 @@ vm-browserify@0.0.4:
|
||||
dependencies:
|
||||
indexof "0.0.1"
|
||||
|
||||
vscode-languageserver-types@^3.14.0:
|
||||
version "3.14.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz#d3b5952246d30e5241592b6dde8280e03942e743"
|
||||
integrity sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==
|
||||
|
||||
w3c-blob@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/w3c-blob/-/w3c-blob-0.0.1.tgz#b0cd352a1a50f515563420ffd5861f950f1d85b8"
|
||||
|
Loading…
Reference in New Issue
Block a user