1import React, { FormEvent, PureComponent } from 'react';
2import { connect, ConnectedProps } from 'react-redux';
3import { css } from '@emotion/css';
4import { InlineField, InlineFieldRow, VerticalGroup } from '@grafana/ui';
5import { selectors } from '@grafana/e2e-selectors';
6import { DataSourcePicker, getTemplateSrv } from '@grafana/runtime';
7import { DataSourceInstanceSettings, getDataSourceRef, LoadingState, SelectableValue } from '@grafana/data';
8
9import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
10import { QueryVariableModel, VariableRefresh, VariableSort, VariableWithMultiSupport } from '../types';
11import { QueryVariableEditorState } from './reducer';
12import { changeQueryVariableDataSource, changeQueryVariableQuery, initQueryVariableEditor } from './actions';
13import { VariableEditorState } from '../editor/reducer';
14import { OnPropChangeArguments, VariableEditorProps } from '../editor/types';
15import { StoreState } from '../../../types';
16import { toVariableIdentifier } from '../state/types';
17import { changeVariableMultiValue } from '../state/actions';
18import { getTimeSrv } from '../../dashboard/services/TimeSrv';
19import { isLegacyQueryEditor, isQueryEditor } from '../guard';
20import { VariableSectionHeader } from '../editor/VariableSectionHeader';
21import { VariableTextField } from '../editor/VariableTextField';
22import { QueryVariableRefreshSelect } from './QueryVariableRefreshSelect';
23import { QueryVariableSortSelect } from './QueryVariableSortSelect';
24
25const mapStateToProps = (state: StoreState) => ({
26  editor: state.templating.editor as VariableEditorState<QueryVariableEditorState>,
27});
28
29const mapDispatchToProps = {
30  initQueryVariableEditor,
31  changeQueryVariableDataSource,
32  changeQueryVariableQuery,
33  changeVariableMultiValue,
34};
35
36const connector = connect(mapStateToProps, mapDispatchToProps);
37
38export interface OwnProps extends VariableEditorProps<QueryVariableModel> {}
39
40export type Props = OwnProps & ConnectedProps<typeof connector>;
41
42export interface State {
43  regex: string | null;
44  tagsQuery: string | null;
45  tagValuesQuery: string | null;
46}
47
48export class QueryVariableEditorUnConnected extends PureComponent<Props, State> {
49  state: State = {
50    regex: null,
51    tagsQuery: null,
52    tagValuesQuery: null,
53  };
54
55  async componentDidMount() {
56    await this.props.initQueryVariableEditor(toVariableIdentifier(this.props.variable));
57  }
58
59  componentDidUpdate(prevProps: Readonly<Props>): void {
60    if (prevProps.variable.datasource !== this.props.variable.datasource) {
61      this.props.changeQueryVariableDataSource(
62        toVariableIdentifier(this.props.variable),
63        this.props.variable.datasource
64      );
65    }
66  }
67
68  onDataSourceChange = (dsSettings: DataSourceInstanceSettings) => {
69    this.props.onPropChange({
70      propName: 'datasource',
71      propValue: dsSettings.isDefault ? null : getDataSourceRef(dsSettings),
72    });
73  };
74
75  onLegacyQueryChange = async (query: any, definition: string) => {
76    if (this.props.variable.query !== query) {
77      this.props.changeQueryVariableQuery(toVariableIdentifier(this.props.variable), query, definition);
78    }
79  };
80
81  onQueryChange = async (query: any) => {
82    if (this.props.variable.query !== query) {
83      let definition = '';
84
85      if (query && query.hasOwnProperty('query') && typeof query.query === 'string') {
86        definition = query.query;
87      }
88
89      this.props.changeQueryVariableQuery(toVariableIdentifier(this.props.variable), query, definition);
90    }
91  };
92
93  onRegExChange = (event: FormEvent<HTMLInputElement>) => {
94    this.setState({ regex: event.currentTarget.value });
95  };
96
97  onRegExBlur = async (event: FormEvent<HTMLInputElement>) => {
98    const regex = event.currentTarget.value;
99    if (this.props.variable.regex !== regex) {
100      this.props.onPropChange({ propName: 'regex', propValue: regex, updateOptions: true });
101    }
102  };
103
104  onRefreshChange = (option: SelectableValue<VariableRefresh>) => {
105    this.props.onPropChange({ propName: 'refresh', propValue: option.value });
106  };
107
108  onSortChange = async (option: SelectableValue<VariableSort>) => {
109    this.props.onPropChange({ propName: 'sort', propValue: option.value, updateOptions: true });
110  };
111
112  onSelectionOptionsChange = async ({ propValue, propName }: OnPropChangeArguments<VariableWithMultiSupport>) => {
113    this.props.onPropChange({ propName, propValue, updateOptions: true });
114  };
115
116  renderQueryEditor = () => {
117    const { editor, variable } = this.props;
118    if (!editor.extended || !editor.extended.dataSource || !editor.extended.VariableQueryEditor) {
119      return null;
120    }
121
122    const query = variable.query;
123    const datasource = editor.extended.dataSource;
124    const VariableQueryEditor = editor.extended.VariableQueryEditor;
125
126    if (isLegacyQueryEditor(VariableQueryEditor, datasource)) {
127      return (
128        <VariableQueryEditor
129          datasource={datasource}
130          query={query}
131          templateSrv={getTemplateSrv()}
132          onChange={this.onLegacyQueryChange}
133        />
134      );
135    }
136
137    const range = getTimeSrv().timeRange();
138
139    if (isQueryEditor(VariableQueryEditor, datasource)) {
140      return (
141        <VariableQueryEditor
142          datasource={datasource}
143          query={query}
144          onChange={this.onQueryChange}
145          onRunQuery={() => {}}
146          data={{ series: [], state: LoadingState.Done, timeRange: range }}
147          range={range}
148          onBlur={() => {}}
149          history={[]}
150        />
151      );
152    }
153
154    return null;
155  };
156
157  render() {
158    return (
159      <VerticalGroup spacing="xs">
160        <VariableSectionHeader name="Query Options" />
161        <VerticalGroup spacing="lg">
162          <VerticalGroup spacing="none">
163            <InlineFieldRow>
164              <InlineField label="Data source" labelWidth={20} htmlFor="data-source-picker">
165                <DataSourcePicker
166                  current={this.props.variable.datasource}
167                  onChange={this.onDataSourceChange}
168                  variables={true}
169                />
170              </InlineField>
171              <QueryVariableRefreshSelect onChange={this.onRefreshChange} refresh={this.props.variable.refresh} />
172            </InlineFieldRow>
173            <div
174              className={css`
175                flex-direction: column;
176                width: 100%;
177              `}
178            >
179              {this.renderQueryEditor()}
180            </div>
181            <VariableTextField
182              value={this.state.regex ?? this.props.variable.regex}
183              name="Regex"
184              placeholder="/.*-(?<text>.*)-(?<value>.*)-.*/"
185              onChange={this.onRegExChange}
186              onBlur={this.onRegExBlur}
187              labelWidth={20}
188              tooltip={
189                <div>
190                  Optional, if you want to extract part of a series name or metric node segment. Named capture groups
191                  can be used to separate the display text and value (
192                  <a
193                    className="external-link"
194                    href="https://grafana.com/docs/grafana/latest/variables/filter-variables-with-regex#filter-and-modify-using-named-text-and-value-capture-groups"
195                    target="__blank"
196                  >
197                    see examples
198                  </a>
199                  ).
200                </div>
201              }
202              ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInput}
203              grow
204            />
205            <QueryVariableSortSelect onChange={this.onSortChange} sort={this.props.variable.sort} />
206          </VerticalGroup>
207
208          <SelectionOptionsEditor
209            variable={this.props.variable}
210            onPropChange={this.onSelectionOptionsChange}
211            onMultiChanged={this.props.changeVariableMultiValue}
212          />
213        </VerticalGroup>
214      </VerticalGroup>
215    );
216  }
217}
218
219export const QueryVariableEditor = connector(QueryVariableEditorUnConnected);
220