FluxQueryEditor.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import { cx, css } from '@emotion/css';
  2. import React, { PureComponent } from 'react';
  3. import { SelectableValue } from '@grafana/data';
  4. import { getTemplateSrv } from '@grafana/runtime';
  5. import {
  6. InlineFormLabel,
  7. LinkButton,
  8. Segment,
  9. CodeEditor,
  10. MonacoEditor,
  11. CodeEditorSuggestionItem,
  12. CodeEditorSuggestionItemKind,
  13. } from '@grafana/ui';
  14. import InfluxDatasource from '../datasource';
  15. import { InfluxQuery } from '../types';
  16. type Props = {
  17. onChange: (query: InfluxQuery) => void;
  18. onRunQuery: () => void;
  19. query: InfluxQuery;
  20. // `datasource` is not used internally, but this component is used at some places
  21. // directly, where the `datasource` prop has to exist. later, when the whole
  22. // query-editor gets converted to react we can stop using this component directly
  23. // and then we can probably remove the datasource attribute.
  24. datasource: InfluxDatasource;
  25. };
  26. const samples: Array<SelectableValue<string>> = [
  27. { label: 'Show buckets', description: 'List the available buckets (table)', value: 'buckets()' },
  28. {
  29. label: 'Simple query',
  30. description: 'filter by measurement and field',
  31. value: `from(bucket: "db/rp")
  32. |> range(start: v.timeRangeStart, stop:v.timeRangeStop)
  33. |> filter(fn: (r) =>
  34. r._measurement == "example-measurement" and
  35. r._field == "example-field"
  36. )`,
  37. },
  38. {
  39. label: 'Grouped Query',
  40. description: 'Group by (min/max/sum/median)',
  41. value: `// v.windowPeriod is a variable referring to the current optimized window period (currently: $interval)
  42. from(bucket: v.bucket)
  43. |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  44. |> filter(fn: (r) => r["_measurement"] == "measurement1" or r["_measurement"] =~ /^.*?regex.*$/)
  45. |> filter(fn: (r) => r["_field"] == "field2" or r["_field"] =~ /^.*?regex.*$/)
  46. |> aggregateWindow(every: v.windowPeriod, fn: mean|median|max|count|derivative|sum)
  47. |> yield(name: "some-name")`,
  48. },
  49. {
  50. label: 'Filter by value',
  51. description: 'Results between a min/max',
  52. value: `// v.bucket, v.timeRangeStart, and v.timeRange stop are all variables supported by the flux plugin and influxdb
  53. from(bucket: v.bucket)
  54. |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  55. |> filter(fn: (r) => r["_value"] >= 10 and r["_value"] <= 20)`,
  56. },
  57. {
  58. label: 'Schema Exploration: (measurements)',
  59. description: 'Get a list of measurement using flux',
  60. value: `import "influxdata/influxdb/v1"
  61. v1.measurements(bucket: v.bucket)`,
  62. },
  63. {
  64. label: 'Schema Exploration: (fields)',
  65. description: 'Return every possible key in a single table',
  66. value: `from(bucket: v.bucket)
  67. |> range(start: v.timeRangeStart, stop:v.timeRangeStop)
  68. |> keys()
  69. |> keep(columns: ["_value"])
  70. |> group()
  71. |> distinct()`,
  72. },
  73. {
  74. label: 'Schema Exploration: (tag keys)',
  75. description: 'Get a list of tag keys using flux',
  76. value: `import "influxdata/influxdb/v1"
  77. v1.tagKeys(bucket: v.bucket)`,
  78. },
  79. {
  80. label: 'Schema Exploration: (tag values)',
  81. description: 'Get a list of tag values using flux',
  82. value: `import "influxdata/influxdb/v1"
  83. v1.tagValues(
  84. bucket: v.bucket,
  85. tag: "host",
  86. predicate: (r) => true,
  87. start: -1d
  88. )`,
  89. },
  90. ];
  91. export class FluxQueryEditor extends PureComponent<Props> {
  92. onFluxQueryChange = (query: string) => {
  93. this.props.onChange({ ...this.props.query, query });
  94. this.props.onRunQuery();
  95. };
  96. onSampleChange = (val: SelectableValue<string>) => {
  97. this.props.onChange({
  98. ...this.props.query,
  99. query: val.value!,
  100. });
  101. // Angular HACK: Since the target does not actually change!
  102. this.forceUpdate();
  103. this.props.onRunQuery();
  104. };
  105. getSuggestions = (): CodeEditorSuggestionItem[] => {
  106. const sugs: CodeEditorSuggestionItem[] = [
  107. {
  108. label: 'v.timeRangeStart',
  109. kind: CodeEditorSuggestionItemKind.Property,
  110. detail: 'The start time',
  111. },
  112. {
  113. label: 'v.timeRangeStop',
  114. kind: CodeEditorSuggestionItemKind.Property,
  115. detail: 'The stop time',
  116. },
  117. {
  118. label: 'v.windowPeriod',
  119. kind: CodeEditorSuggestionItemKind.Property,
  120. detail: 'based on max data points',
  121. },
  122. {
  123. label: 'v.defaultBucket',
  124. kind: CodeEditorSuggestionItemKind.Property,
  125. detail: 'bucket configured in the datsource',
  126. },
  127. {
  128. label: 'v.organization',
  129. kind: CodeEditorSuggestionItemKind.Property,
  130. detail: 'org configured for the datsource',
  131. },
  132. ];
  133. const templateSrv = getTemplateSrv();
  134. templateSrv.getVariables().forEach((variable) => {
  135. const label = '${' + variable.name + '}';
  136. let val = templateSrv.replace(label);
  137. if (val === label) {
  138. val = '';
  139. }
  140. sugs.push({
  141. label,
  142. kind: CodeEditorSuggestionItemKind.Text,
  143. detail: `(Template Variable) ${val}`,
  144. });
  145. });
  146. return sugs;
  147. };
  148. // For some reason in angular, when this component gets re-mounted, the width
  149. // is not set properly. This forces the layout shortly after mount so that it
  150. // displays OK. Note: this is not an issue when used directly in react
  151. editorDidMountCallbackHack = (editor: MonacoEditor) => {
  152. setTimeout(() => editor.layout(), 100);
  153. };
  154. render() {
  155. const { query } = this.props;
  156. const helpTooltip = (
  157. <div>
  158. Type: <i>ctrl+space</i> to show template variable suggestions <br />
  159. Many queries can be copied from Chronograf
  160. </div>
  161. );
  162. return (
  163. <>
  164. <CodeEditor
  165. height={'200px'}
  166. language="sql"
  167. value={query.query || ''}
  168. onBlur={this.onFluxQueryChange}
  169. onSave={this.onFluxQueryChange}
  170. showMiniMap={false}
  171. showLineNumbers={true}
  172. getSuggestions={this.getSuggestions}
  173. onEditorDidMount={this.editorDidMountCallbackHack}
  174. />
  175. <div
  176. className={cx(
  177. 'gf-form-inline',
  178. css`
  179. margin-top: 6px;
  180. `
  181. )}
  182. >
  183. <LinkButton
  184. icon="external-link-alt"
  185. variant="secondary"
  186. target="blank"
  187. href="https://docs.influxdata.com/influxdb/latest/query-data/get-started/"
  188. >
  189. Flux language syntax
  190. </LinkButton>
  191. <Segment options={samples} value="Sample Query" onChange={this.onSampleChange} />
  192. <div className="gf-form gf-form--grow">
  193. <div className="gf-form-label gf-form-label--grow"></div>
  194. </div>
  195. <InlineFormLabel width={5} tooltip={helpTooltip}>
  196. Help
  197. </InlineFormLabel>
  198. </div>
  199. </>
  200. );
  201. }
  202. }