Преглед изворни кода

refactor Explore query field

David Kaltschmidt пре 7 година
родитељ
комит
0425b47791

+ 16 - 10
public/app/containers/Explore/QueryField.tsx

@@ -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}

+ 4 - 0
public/app/containers/Explore/QueryRows.tsx

@@ -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>

+ 11 - 10
public/app/containers/Explore/slate-plugins/prism/index.tsx

@@ -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();

+ 1 - 0
public/sass/_grafana.scss

@@ -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 - 0
public/sass/components/_slate_editor.scss

@@ -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;
+  }
+}

+ 0 - 147
public/sass/pages/_explore.scss

@@ -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;
-  }
-}