mirror of
https://github.com/grafana/grafana.git
synced 2025-01-08 23:23:45 -06:00
refactor Explore query field
This commit is contained in:
parent
0107491195
commit
0425b47791
@ -9,7 +9,7 @@ import { getNextCharacter, getPreviousCousin } from './utils/dom';
|
||||
import BracesPlugin from './slate-plugins/braces';
|
||||
import ClearPlugin from './slate-plugins/clear';
|
||||
import NewlinePlugin from './slate-plugins/newline';
|
||||
import PluginPrism, { configurePrismMetricsTokens } from './slate-plugins/prism/index';
|
||||
import PluginPrism, { setPrismTokens } from './slate-plugins/prism/index';
|
||||
import RunnerPlugin from './slate-plugins/runner';
|
||||
import debounce from './utils/debounce';
|
||||
import { processLabels, RATE_RANGES, cleanText } from './utils/prometheus';
|
||||
@ -17,13 +17,13 @@ import { processLabels, RATE_RANGES, cleanText } from './utils/prometheus';
|
||||
import Typeahead from './Typeahead';
|
||||
|
||||
const EMPTY_METRIC = '';
|
||||
const TYPEAHEAD_DEBOUNCE = 300;
|
||||
export const TYPEAHEAD_DEBOUNCE = 300;
|
||||
|
||||
function flattenSuggestions(s) {
|
||||
return s ? s.reduce((acc, g) => acc.concat(g.items), []) : [];
|
||||
}
|
||||
|
||||
const getInitialValue = query =>
|
||||
export const getInitialValue = query =>
|
||||
Value.fromJSON({
|
||||
document: {
|
||||
nodes: [
|
||||
@ -45,12 +45,14 @@ const getInitialValue = query =>
|
||||
},
|
||||
});
|
||||
|
||||
class Portal extends React.Component {
|
||||
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('explore-typeahead', `explore-typeahead-${props.index}`);
|
||||
this.node.classList.add(`slate-typeahead`, `slate-typeahead-${prefix}-${index}`);
|
||||
document.body.appendChild(this.node);
|
||||
}
|
||||
|
||||
@ -71,12 +73,14 @@ class QueryField extends React.Component<any, any> {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
const { prismDefinition = {}, prismLanguage = 'promql' } = props;
|
||||
|
||||
this.plugins = [
|
||||
BracesPlugin(),
|
||||
ClearPlugin(),
|
||||
RunnerPlugin({ handler: props.onPressEnter }),
|
||||
NewlinePlugin(),
|
||||
PluginPrism(),
|
||||
PluginPrism({ definition: prismDefinition, language: prismLanguage }),
|
||||
];
|
||||
|
||||
this.state = {
|
||||
@ -131,7 +135,8 @@ class QueryField extends React.Component<any, any> {
|
||||
if (!this.state.metrics) {
|
||||
return;
|
||||
}
|
||||
configurePrismMetricsTokens(this.state.metrics);
|
||||
setPrismTokens(this.props.language, 'metrics', this.state.metrics);
|
||||
|
||||
// Trigger re-render
|
||||
window.requestAnimationFrame(() => {
|
||||
// Bogus edit to trigger highlighting
|
||||
@ -162,7 +167,7 @@ class QueryField extends React.Component<any, any> {
|
||||
const selection = window.getSelection();
|
||||
if (selection.anchorNode) {
|
||||
const wrapperNode = selection.anchorNode.parentElement;
|
||||
const editorNode = wrapperNode.closest('.query-field');
|
||||
const editorNode = wrapperNode.closest('.slate-query-field');
|
||||
if (!editorNode || this.state.value.isBlurred) {
|
||||
// Not inside this editor
|
||||
return;
|
||||
@ -514,6 +519,7 @@ class QueryField extends React.Component<any, any> {
|
||||
};
|
||||
|
||||
renderMenu = () => {
|
||||
const { portalPrefix } = this.props;
|
||||
const { suggestions } = this.state;
|
||||
const hasSuggesstions = suggestions && suggestions.length > 0;
|
||||
if (!hasSuggesstions) {
|
||||
@ -528,7 +534,7 @@ class QueryField extends React.Component<any, any> {
|
||||
|
||||
// Create typeahead in DOM root so we can later position it absolutely
|
||||
return (
|
||||
<Portal>
|
||||
<Portal prefix={portalPrefix}>
|
||||
<Typeahead
|
||||
menuRef={this.menuRef}
|
||||
selectedItems={selectedKeys}
|
||||
@ -541,7 +547,7 @@ class QueryField extends React.Component<any, any> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="query-field">
|
||||
<div className="slate-query-field">
|
||||
{this.renderMenu()}
|
||||
<Editor
|
||||
autoCorrect={false}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import promql from './slate-plugins/prism/promql';
|
||||
import QueryField from './QueryField';
|
||||
|
||||
class QueryRow extends PureComponent<any, any> {
|
||||
@ -58,9 +59,12 @@ class QueryRow extends PureComponent<any, any> {
|
||||
<div className="query-field-wrapper">
|
||||
<QueryField
|
||||
initialQuery={edited ? null : query}
|
||||
portalPrefix="explore"
|
||||
onPressEnter={this.handlePressEnter}
|
||||
onQueryChange={this.handleChangeQuery}
|
||||
placeholder="Enter a PromQL query"
|
||||
prismLanguage="promql"
|
||||
prismDefinition={promql}
|
||||
request={request}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,16 +1,12 @@
|
||||
import React from 'react';
|
||||
import Prism from 'prismjs';
|
||||
|
||||
import Promql from './promql';
|
||||
|
||||
Prism.languages.promql = Promql;
|
||||
|
||||
const TOKEN_MARK = 'prism-token';
|
||||
|
||||
export function configurePrismMetricsTokens(metrics) {
|
||||
Prism.languages.promql.metric = {
|
||||
alias: 'variable',
|
||||
pattern: new RegExp(`(?:^|\\s)(${metrics.join('|')})(?:$|\\s)`),
|
||||
export function setPrismTokens(language, field, values, alias = 'variable') {
|
||||
Prism.languages[language][field] = {
|
||||
alias,
|
||||
pattern: new RegExp(`(?:^|\\s)(${values.join('|')})(?:$|\\s)`),
|
||||
};
|
||||
}
|
||||
|
||||
@ -21,7 +17,12 @@ export function configurePrismMetricsTokens(metrics) {
|
||||
* (Adapted to handle nested grammar definitions.)
|
||||
*/
|
||||
|
||||
export default function PrismPlugin() {
|
||||
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
|
||||
@ -54,7 +55,7 @@ export default function PrismPlugin() {
|
||||
|
||||
const texts = node.getTexts().toArray();
|
||||
const tstring = texts.map(t => t.text).join('\n');
|
||||
const grammar = Prism.languages.promql;
|
||||
const grammar = Prism.languages[language];
|
||||
const tokens = Prism.tokenize(tstring, grammar);
|
||||
const decorations = [];
|
||||
let startText = texts.shift();
|
||||
|
@ -67,6 +67,7 @@
|
||||
@import 'components/filter-list';
|
||||
@import 'components/filter-table';
|
||||
@import 'components/old_stuff';
|
||||
@import 'components/slate_editor';
|
||||
@import 'components/typeahead';
|
||||
@import 'components/modals';
|
||||
@import 'components/dropdown';
|
||||
|
146
public/sass/components/_slate_editor.scss
Normal file
146
public/sass/components/_slate_editor.scss
Normal file
@ -0,0 +1,146 @@
|
||||
.slate-query-field {
|
||||
font-size: $font-size-root;
|
||||
font-family: $font-family-monospace;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.slate-query-field-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 6px 7px 4px;
|
||||
width: 100%;
|
||||
cursor: text;
|
||||
line-height: $line-height-base;
|
||||
color: $text-color-weak;
|
||||
background-color: $panel-bg;
|
||||
background-image: none;
|
||||
border: $panel-border;
|
||||
border-radius: $border-radius;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.slate-typeahead {
|
||||
.typeahead {
|
||||
position: absolute;
|
||||
z-index: auto;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
opacity: 0;
|
||||
border-radius: $border-radius;
|
||||
transition: opacity 0.75s;
|
||||
border: $panel-border;
|
||||
max-height: calc(66vh);
|
||||
overflow-y: scroll;
|
||||
max-width: calc(66%);
|
||||
overflow-x: hidden;
|
||||
outline: none;
|
||||
list-style: none;
|
||||
background: $panel-bg;
|
||||
color: $text-color;
|
||||
transition: opacity 0.4s ease-out;
|
||||
box-shadow: $typeahead-shadow;
|
||||
}
|
||||
|
||||
.typeahead-group__title {
|
||||
color: $text-color-weak;
|
||||
font-size: $font-size-sm;
|
||||
line-height: $line-height-base;
|
||||
padding: $input-padding-y $input-padding-x;
|
||||
}
|
||||
|
||||
.typeahead-item {
|
||||
height: auto;
|
||||
font-family: $font-family-monospace;
|
||||
padding: $input-padding-y $input-padding-x;
|
||||
padding-left: $input-padding-x-lg;
|
||||
font-size: $font-size-sm;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
|
||||
background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), padding 0.15s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
|
||||
.typeahead-item__selected {
|
||||
background-color: $typeahead-selected-bg;
|
||||
color: $typeahead-selected-color;
|
||||
}
|
||||
}
|
||||
|
||||
/* SYNTAX */
|
||||
|
||||
.slate-query-field {
|
||||
.token.comment,
|
||||
.token.block-comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: $text-color-weak;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: $text-color-weak;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.function-name,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: $query-red;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.function,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: $query-green;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.token.variable {
|
||||
color: $query-purple;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword,
|
||||
.token.class-name {
|
||||
color: $query-blue;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important {
|
||||
color: $query-orange;
|
||||
}
|
||||
|
||||
.token.important {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
@ -93,150 +93,3 @@
|
||||
.query-row-tools {
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
.query-field {
|
||||
font-size: $font-size-root;
|
||||
font-family: $font-family-monospace;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.query-field-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 6px 7px 4px;
|
||||
width: 100%;
|
||||
cursor: text;
|
||||
line-height: $line-height-base;
|
||||
color: $text-color-weak;
|
||||
background-color: $panel-bg;
|
||||
background-image: none;
|
||||
border: $panel-border;
|
||||
border-radius: $border-radius;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.explore-typeahead {
|
||||
.typeahead {
|
||||
position: absolute;
|
||||
z-index: auto;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
opacity: 0;
|
||||
border-radius: $border-radius;
|
||||
transition: opacity 0.75s;
|
||||
border: $panel-border;
|
||||
max-height: calc(66vh);
|
||||
overflow-y: scroll;
|
||||
max-width: calc(66%);
|
||||
overflow-x: hidden;
|
||||
outline: none;
|
||||
list-style: none;
|
||||
background: $panel-bg;
|
||||
color: $text-color;
|
||||
transition: opacity 0.4s ease-out;
|
||||
box-shadow: $typeahead-shadow;
|
||||
}
|
||||
|
||||
.typeahead-group__title {
|
||||
color: $text-color-weak;
|
||||
font-size: $font-size-sm;
|
||||
line-height: $line-height-base;
|
||||
padding: $input-padding-y $input-padding-x;
|
||||
}
|
||||
|
||||
.typeahead-item {
|
||||
height: auto;
|
||||
font-family: $font-family-monospace;
|
||||
padding: $input-padding-y $input-padding-x;
|
||||
padding-left: $input-padding-x-lg;
|
||||
font-size: $font-size-sm;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
|
||||
background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), padding 0.15s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
|
||||
.typeahead-item__selected {
|
||||
background-color: $typeahead-selected-bg;
|
||||
color: $typeahead-selected-color;
|
||||
}
|
||||
}
|
||||
|
||||
/* SYNTAX */
|
||||
|
||||
.explore {
|
||||
.token.comment,
|
||||
.token.block-comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: $text-color-weak;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: $text-color-weak;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.function-name,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: $query-red;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.function,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: $query-green;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.token.variable {
|
||||
color: $query-purple;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword,
|
||||
.token.class-name {
|
||||
color: $query-blue;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important {
|
||||
color: $query-orange;
|
||||
}
|
||||
|
||||
.token.important {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user