Merge pull request #3659 from influxdata/code-mirror-data-explorer
Upgrade Data Explorer Query Field with CodeMirror2pull/10616/head
commit
493ffb264a
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
1. [#3474](https://github.com/influxdata/chronograf/pull/3474): Sort task table on Manage Alert page alphabetically
|
1. [#3474](https://github.com/influxdata/chronograf/pull/3474): Sort task table on Manage Alert page alphabetically
|
||||||
1. [#3590](https://github.com/influxdata/chronograf/pull/3590): Redesign icons in side navigation
|
1. [#3590](https://github.com/influxdata/chronograf/pull/3590): Redesign icons in side navigation
|
||||||
|
1. [#3659](https://github.com/influxdata/chronograf/pull/3659): Upgrade Data Explorer query text field with syntax highlighting and partial multi-line support
|
||||||
1. [#3663](https://github.com/influxdata/chronograf/pull/3663): Truncate message preview in Alert Rules table
|
1. [#3663](https://github.com/influxdata/chronograf/pull/3663): Truncate message preview in Alert Rules table
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import React, {PureComponent, KeyboardEvent} from 'react'
|
import React, {PureComponent} from 'react'
|
||||||
|
|
||||||
|
import {Controlled as ReactCodeMirror, IInstance} from 'react-codemirror2'
|
||||||
|
import {EditorChange} from 'codemirror'
|
||||||
import Dropdown from 'src/shared/components/Dropdown'
|
import Dropdown from 'src/shared/components/Dropdown'
|
||||||
|
import classnames from 'classnames'
|
||||||
import {QUERY_TEMPLATES, QueryTemplate} from 'src/data_explorer/constants'
|
import {QUERY_TEMPLATES, QueryTemplate} from 'src/data_explorer/constants'
|
||||||
import QueryStatus from 'src/shared/components/QueryStatus'
|
import QueryStatus from 'src/shared/components/QueryStatus'
|
||||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
import {QueryConfig} from 'src/types'
|
import {QueryConfig} from 'src/types'
|
||||||
|
import 'src/external/codemirror'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
query: string
|
query: string
|
||||||
|
@ -14,19 +18,17 @@ interface Props {
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
value: string
|
value: string
|
||||||
|
focused: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ErrorHandling
|
@ErrorHandling
|
||||||
class QueryEditor extends PureComponent<Props, State> {
|
class QueryEditor extends PureComponent<Props, State> {
|
||||||
private editor: React.RefObject<HTMLTextAreaElement>
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
value: this.props.query,
|
value: this.props.query,
|
||||||
|
focused: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editor = React.createRef<HTMLTextAreaElement>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillReceiveProps(nextProps: Props) {
|
public componentWillReceiveProps(nextProps: Props) {
|
||||||
|
@ -41,21 +43,34 @@ class QueryEditor extends PureComponent<Props, State> {
|
||||||
} = this.props
|
} = this.props
|
||||||
const {value} = this.state
|
const {value} = this.state
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
tabIndex: 1,
|
||||||
|
mode: 'influxQL',
|
||||||
|
readonly: false,
|
||||||
|
lineNumbers: false,
|
||||||
|
autoRefresh: true,
|
||||||
|
theme: 'influxql',
|
||||||
|
completeSingle: false,
|
||||||
|
lineWrapping: true,
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="query-editor">
|
<div className="query-editor">
|
||||||
<textarea
|
<div className={this.queryCodeClassName}>
|
||||||
className="query-editor--field"
|
<ReactCodeMirror
|
||||||
ref={this.editor}
|
autoFocus={true}
|
||||||
value={value}
|
autoCursor={true}
|
||||||
autoComplete="off"
|
value={value}
|
||||||
spellCheck={false}
|
options={options}
|
||||||
onBlur={this.handleUpdate}
|
onBeforeChange={this.updateCode}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
onKeyDown={this.handleKeyDown}
|
onTouchStart={this.onTouchStart}
|
||||||
data-test="query-editor-field"
|
onBlur={this.handleBlur}
|
||||||
placeholder="Enter a query or select database, measurement, and field below and have us build one for you..."
|
onFocus={this.handleFocus}
|
||||||
/>
|
onKeyUp={this.handleKeyUp}
|
||||||
<div className="varmoji">
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={this.varmojiClassName}>
|
||||||
<div className="varmoji-container">
|
<div className="varmoji-container">
|
||||||
<div className="varmoji-front">
|
<div className="varmoji-front">
|
||||||
<QueryStatus status={status}>
|
<QueryStatus status={status}>
|
||||||
|
@ -66,6 +81,12 @@ class QueryEditor extends PureComponent<Props, State> {
|
||||||
className="dropdown-140 query-editor--templates"
|
className="dropdown-140 query-editor--templates"
|
||||||
buttonSize="btn-xs"
|
buttonSize="btn-xs"
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
className="btn btn-xs btn-primary query-editor--submit"
|
||||||
|
onClick={this.handleSubmit}
|
||||||
|
>
|
||||||
|
Submit Query
|
||||||
|
</button>
|
||||||
</QueryStatus>
|
</QueryStatus>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,32 +95,54 @@ class QueryEditor extends PureComponent<Props, State> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>): void => {
|
private handleSubmit = (): void => {
|
||||||
const {value} = this.state
|
|
||||||
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
e.preventDefault()
|
|
||||||
this.setState({value})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
e.preventDefault()
|
|
||||||
this.handleUpdate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleChange = (): void => {
|
|
||||||
const value = this.editor.current.value
|
|
||||||
this.setState({value})
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleUpdate = (): void => {
|
|
||||||
this.props.onUpdate(this.state.value)
|
this.props.onUpdate(this.state.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get queryCodeClassName(): string {
|
||||||
|
const {focused} = this.state
|
||||||
|
|
||||||
|
return classnames('query-editor--code', {focus: focused})
|
||||||
|
}
|
||||||
|
|
||||||
|
private get varmojiClassName(): string {
|
||||||
|
const {focused} = this.state
|
||||||
|
|
||||||
|
return classnames('varmoji', {focus: focused})
|
||||||
|
}
|
||||||
|
|
||||||
|
private onTouchStart = () => {}
|
||||||
|
|
||||||
|
private handleChange = (): void => {}
|
||||||
|
|
||||||
|
private handleBlur = (): void => {
|
||||||
|
this.setState({focused: false})
|
||||||
|
this.handleSubmit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleFocus = (): void => {
|
||||||
|
this.setState({focused: true})
|
||||||
|
}
|
||||||
|
|
||||||
private handleChooseMetaQuery = (template: QueryTemplate): void => {
|
private handleChooseMetaQuery = (template: QueryTemplate): void => {
|
||||||
this.setState({value: template.query})
|
this.setState({value: template.query})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleKeyUp = (__, e: KeyboardEvent) => {
|
||||||
|
const {ctrlKey, metaKey, key} = e
|
||||||
|
|
||||||
|
if (key === 'Enter' && (ctrlKey || metaKey)) {
|
||||||
|
this.handleSubmit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateCode = (
|
||||||
|
_: IInstance,
|
||||||
|
__: EditorChange,
|
||||||
|
value: string
|
||||||
|
): void => {
|
||||||
|
this.setState({value})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default QueryEditor
|
export default QueryEditor
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
import {modeFlux, modeTickscript} from 'src/shared/constants/codeMirrorModes'
|
import {
|
||||||
|
modeFlux,
|
||||||
|
modeTickscript,
|
||||||
|
modeInfluxQL,
|
||||||
|
} from 'src/shared/constants/codeMirrorModes'
|
||||||
import 'codemirror/addon/hint/show-hint'
|
import 'codemirror/addon/hint/show-hint'
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
@ -314,4 +318,5 @@ function indentFunction(states, meta) {
|
||||||
|
|
||||||
// Modes
|
// Modes
|
||||||
CodeMirror.defineSimpleMode('flux', modeFlux)
|
CodeMirror.defineSimpleMode('flux', modeFlux)
|
||||||
CodeMirror.defineSimpleMode('tickscript', modeTickscript)
|
CodeMirror.defineSimpleMode('tickscript', modeTickscript)
|
||||||
|
CodeMirror.defineSimpleMode('influxQL', modeInfluxQL)
|
|
@ -174,3 +174,80 @@ export const modeTickscript = {
|
||||||
lineComment: '//',
|
lineComment: '//',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const modeInfluxQL = {
|
||||||
|
// The start state contains the rules that are intially used
|
||||||
|
start: [
|
||||||
|
// The regex matches the token, the token property contains the type
|
||||||
|
{
|
||||||
|
regex: /"(?:[^\\]|\\.)*?(?:"|$)/,
|
||||||
|
token: 'string.double',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /'(?:[^\\]|\\.)*?(?:'|$)/,
|
||||||
|
token: 'string.single',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /(function)(\s+)([a-z$][\w$]*)/,
|
||||||
|
token: ['keyword', null, 'variable-2'],
|
||||||
|
},
|
||||||
|
// Rules are matched in the order in which they appear, so there is
|
||||||
|
// no ambiguity between this one and the one above
|
||||||
|
{
|
||||||
|
regex: /(SELECT\s|AS\s|FROM\s|WHERE\s|GROUP\sBY\s)/i,
|
||||||
|
token: 'clause',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /FILL(?=[(])/i,
|
||||||
|
token: 'clause',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /(CREATE\s|SHOW\s|DROP\s)/i,
|
||||||
|
token: 'meta',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i,
|
||||||
|
token: 'number',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /[:](interval|dashboardTime|dashboardUpper)[:]/,
|
||||||
|
token: 'temp-system',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /[:]\w+[:]/,
|
||||||
|
token: 'temp-var',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /now[(][)]\s\S\s\S+/,
|
||||||
|
token: 'now',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /[-+\/*=~<>!]+/,
|
||||||
|
token: 'operator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /(NULL)/i,
|
||||||
|
token: 'null',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// The multi-line comment state.
|
||||||
|
comment: [
|
||||||
|
{
|
||||||
|
regex: /.*?\*\//,
|
||||||
|
token: 'comment',
|
||||||
|
next: 'start',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /.*/,
|
||||||
|
token: 'comment',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// The meta property contains global information about the mode. It
|
||||||
|
// can contain properties like lineComment, which are supported by
|
||||||
|
// all modes, and also directives like dontIndentStates, which are
|
||||||
|
// specific to simple modes.
|
||||||
|
meta: {
|
||||||
|
dontIndentStates: ['comment'],
|
||||||
|
lineComment: '//',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
CodeMirror "InfluxQL" Theme
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
Intended for use with the influxQL CodeMirror Mode
|
||||||
|
*/
|
||||||
|
|
||||||
|
.cm-s-influxql {
|
||||||
|
color: $g11-sidewalk;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
.cm-clause {
|
||||||
|
color: $c-pool;
|
||||||
|
}
|
||||||
|
.cm-meta {
|
||||||
|
color: $c-honeydew;
|
||||||
|
}
|
||||||
|
.cm-string {
|
||||||
|
color: $g15-platinum;
|
||||||
|
}
|
||||||
|
.cm-comment {
|
||||||
|
color: $g8-storm;
|
||||||
|
}
|
||||||
|
.cm-number {
|
||||||
|
color: $c-pineapple;
|
||||||
|
}
|
||||||
|
.cm-operator {
|
||||||
|
color: $c-pool;
|
||||||
|
}
|
||||||
|
.cm-temp-var {
|
||||||
|
color: $c-comet;
|
||||||
|
}
|
||||||
|
.cm-temp-system {
|
||||||
|
color: #ff4d9e ;
|
||||||
|
}
|
||||||
|
.cm-now {
|
||||||
|
color: $c-pineapple;
|
||||||
|
}
|
||||||
|
.cm-null {
|
||||||
|
color: $g8-storm;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
|
@ -90,4 +90,5 @@ div.CodeMirror-selected,
|
||||||
*/
|
*/
|
||||||
@import 'time-machine';
|
@import 'time-machine';
|
||||||
@import 'tickscript';
|
@import 'tickscript';
|
||||||
|
@import 'influxql';
|
||||||
@import 'hints';
|
@import 'hints';
|
||||||
|
|
|
@ -13,40 +13,15 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 3; /* Minimum amount to obcure the toggle flip within Query Builder. Will fix later */
|
z-index: 3; /* Minimum amount to obcure the toggle flip within Query Builder. Will fix later */
|
||||||
}
|
}
|
||||||
.query-editor--field {
|
.query-editor--code {
|
||||||
font-family: $code-font;
|
background-color: $query-editor--field-bg;
|
||||||
font-size: 12px;
|
|
||||||
line-height: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
word-wrap: break-word;
|
|
||||||
word-break: break-all;
|
|
||||||
@include custom-scrollbar($query-editor--field-bg, $query-editor--field-text);
|
|
||||||
display: block;
|
|
||||||
resize: none;
|
|
||||||
width: 100%;
|
|
||||||
height: $query-editor--field-height;
|
|
||||||
transition:
|
|
||||||
color 0.25s ease,
|
|
||||||
background-color 0.25s ease,
|
|
||||||
border-color 0.25s ease;
|
|
||||||
border: 2px solid $query-editor--bg;
|
border: 2px solid $query-editor--bg;
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
background-color: $query-editor--field-bg;
|
|
||||||
color: $query-editor--field-text;
|
|
||||||
padding: 12px 10px 0 10px;
|
|
||||||
border-radius: $radius $radius 0 0;
|
border-radius: $radius $radius 0 0;
|
||||||
margin: 0;
|
padding: 6px 8px;
|
||||||
|
transition: border-color 0.25s ease;
|
||||||
|
|
||||||
&:hover,
|
&.focus {
|
||||||
&:hover + .query-editor--status {
|
|
||||||
border-color: $query-editor--bg;
|
|
||||||
}
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
color: $query-editor--field-text !important;
|
|
||||||
border-color: $c-pool;
|
|
||||||
}
|
|
||||||
&:focus + .varmoji {
|
|
||||||
border-color: $c-pool;
|
border-color: $c-pool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,6 +71,9 @@
|
||||||
max-width: $query-editor--templates-menu-width;
|
max-width: $query-editor--templates-menu-width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
button.btn.query-editor--submit {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -113,6 +91,10 @@
|
||||||
height: $query-editor--status-height;
|
height: $query-editor--status-height;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
perspective: 1000px;
|
perspective: 1000px;
|
||||||
|
|
||||||
|
&.focus {
|
||||||
|
border-color: $c-pool;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.varmoji-container {
|
.varmoji-container {
|
||||||
transition: transform 0.6s ease;
|
transition: transform 0.6s ease;
|
||||||
|
|
Loading…
Reference in New Issue