PromQueryEditor.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import { map } from 'lodash';
  2. import React, { PureComponent } from 'react';
  3. // Types
  4. import { CoreApp, SelectableValue } from '@grafana/data';
  5. import { InlineFormLabel, LegacyForms, Select } from '@grafana/ui';
  6. import { PromQuery } from '../types';
  7. import { PromExemplarField } from './PromExemplarField';
  8. import PromLink from './PromLink';
  9. import PromQueryField from './PromQueryField';
  10. import { PromQueryEditorProps } from './types';
  11. const { Switch } = LegacyForms;
  12. export const FORMAT_OPTIONS: Array<SelectableValue<string>> = [
  13. { label: 'Time series', value: 'time_series' },
  14. { label: 'Table', value: 'table' },
  15. { label: 'Heatmap', value: 'heatmap' },
  16. ];
  17. export const INTERVAL_FACTOR_OPTIONS: Array<SelectableValue<number>> = map([1, 2, 3, 4, 5, 10], (value: number) => ({
  18. value,
  19. label: '1/' + value,
  20. }));
  21. interface State {
  22. legendFormat?: string;
  23. formatOption: SelectableValue<string>;
  24. interval?: string;
  25. intervalFactorOption: SelectableValue<number>;
  26. instant: boolean;
  27. exemplar: boolean;
  28. }
  29. export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State> {
  30. // Query target to be modified and used for queries
  31. query: PromQuery;
  32. constructor(props: PromQueryEditorProps) {
  33. super(props);
  34. // Use default query to prevent undefined input values
  35. const defaultQuery: Partial<PromQuery> = {
  36. expr: '',
  37. legendFormat: '',
  38. interval: '',
  39. // Set exemplar to false for alerting queries
  40. exemplar: props.app === CoreApp.UnifiedAlerting ? false : true,
  41. };
  42. const query = Object.assign({}, defaultQuery, props.query);
  43. this.query = query;
  44. // Query target properties that are fully controlled inputs
  45. this.state = {
  46. // Fully controlled text inputs
  47. interval: query.interval,
  48. legendFormat: query.legendFormat,
  49. // Select options
  50. formatOption: FORMAT_OPTIONS.find((option) => option.value === query.format) || FORMAT_OPTIONS[0],
  51. intervalFactorOption:
  52. INTERVAL_FACTOR_OPTIONS.find((option) => option.value === query.intervalFactor) || INTERVAL_FACTOR_OPTIONS[0],
  53. // Switch options
  54. instant: Boolean(query.instant),
  55. exemplar: Boolean(query.exemplar),
  56. };
  57. }
  58. onFieldChange = (query: PromQuery, override?: any) => {
  59. this.query.expr = query.expr;
  60. };
  61. onFormatChange = (option: SelectableValue<string>) => {
  62. this.query.format = option.value;
  63. this.setState({ formatOption: option }, this.onRunQuery);
  64. };
  65. onInstantChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
  66. const instant = (e.target as HTMLInputElement).checked;
  67. this.query.instant = instant;
  68. this.setState({ instant }, this.onRunQuery);
  69. };
  70. onIntervalChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
  71. const interval = e.currentTarget.value;
  72. this.query.interval = interval;
  73. this.setState({ interval });
  74. };
  75. onIntervalFactorChange = (option: SelectableValue<number>) => {
  76. this.query.intervalFactor = option.value;
  77. this.setState({ intervalFactorOption: option }, this.onRunQuery);
  78. };
  79. onLegendChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
  80. const legendFormat = e.currentTarget.value;
  81. this.query.legendFormat = legendFormat;
  82. this.setState({ legendFormat });
  83. };
  84. onExemplarChange = (isEnabled: boolean) => {
  85. this.query.exemplar = isEnabled;
  86. this.setState({ exemplar: isEnabled }, this.onRunQuery);
  87. };
  88. onRunQuery = () => {
  89. const { query } = this;
  90. // Change of query.hide happens outside of this component and is just passed as prop. We have to update it when running queries.
  91. const { hide } = this.props.query;
  92. this.props.onChange({ ...query, hide });
  93. this.props.onRunQuery();
  94. };
  95. render() {
  96. const { datasource, query, range, data } = this.props;
  97. const { formatOption, instant, interval, intervalFactorOption, legendFormat } = this.state;
  98. //We want to hide exemplar field for unified alerting as exemplars in alerting don't make sense and are source of confusion
  99. const showExemplarField = this.props.app !== CoreApp.UnifiedAlerting;
  100. return (
  101. <PromQueryField
  102. datasource={datasource}
  103. query={query}
  104. range={range}
  105. onRunQuery={this.onRunQuery}
  106. onChange={this.onFieldChange}
  107. history={[]}
  108. data={data}
  109. data-testid={testIds.editor}
  110. ExtraFieldElement={
  111. <div className="gf-form-inline">
  112. <div className="gf-form">
  113. <InlineFormLabel
  114. width={7}
  115. tooltip="Controls the name of the time series, using name or pattern. For example
  116. {{hostname}} will be replaced with label value for the label hostname."
  117. >
  118. Legend
  119. </InlineFormLabel>
  120. <input
  121. type="text"
  122. className="gf-form-input"
  123. placeholder="legend format"
  124. value={legendFormat}
  125. onChange={this.onLegendChange}
  126. onBlur={this.onRunQuery}
  127. />
  128. </div>
  129. <div className="gf-form">
  130. <InlineFormLabel
  131. width={7}
  132. tooltip={
  133. <>
  134. An additional lower limit for the step parameter of the Prometheus query and for the{' '}
  135. <code>$__interval</code> and <code>$__rate_interval</code> variables. The limit is absolute and not
  136. modified by the &quot;Resolution&quot; setting.
  137. </>
  138. }
  139. >
  140. Min step
  141. </InlineFormLabel>
  142. <input
  143. type="text"
  144. className="gf-form-input width-8"
  145. aria-label="Set lower limit for the step parameter"
  146. placeholder={interval}
  147. onChange={this.onIntervalChange}
  148. onBlur={this.onRunQuery}
  149. value={interval}
  150. />
  151. </div>
  152. <div className="gf-form">
  153. <div className="gf-form-label">Resolution</div>
  154. <Select
  155. aria-label="Select resolution"
  156. isSearchable={false}
  157. options={INTERVAL_FACTOR_OPTIONS}
  158. onChange={this.onIntervalFactorChange}
  159. value={intervalFactorOption}
  160. />
  161. </div>
  162. <div className="gf-form">
  163. <div className="gf-form-label width-7">Format</div>
  164. <Select
  165. className="select-container"
  166. width={16}
  167. isSearchable={false}
  168. options={FORMAT_OPTIONS}
  169. onChange={this.onFormatChange}
  170. value={formatOption}
  171. aria-label="Select format"
  172. />
  173. <Switch label="Instant" checked={instant} onChange={this.onInstantChange} />
  174. <InlineFormLabel width={10} tooltip="Link to Graph in Prometheus">
  175. <PromLink
  176. datasource={datasource}
  177. query={this.query} // Use modified query
  178. panelData={data}
  179. />
  180. </InlineFormLabel>
  181. </div>
  182. {showExemplarField && (
  183. <PromExemplarField
  184. onChange={this.onExemplarChange}
  185. datasource={datasource}
  186. query={this.query}
  187. data-testid={testIds.exemplar}
  188. />
  189. )}
  190. </div>
  191. }
  192. />
  193. );
  194. }
  195. }
  196. export const testIds = {
  197. editor: 'prom-editor',
  198. exemplar: 'exemplar-editor',
  199. };