1import React, { PureComponent } from 'react';
2
3import { getTemplateSrv } from '@grafana/runtime';
4import { QueryEditorProps } from '@grafana/data';
5
6import { VariableQueryField } from './';
7import { extractServicesFromMetricDescriptors, getLabelKeys, getMetricTypes } from '../functions';
8import {
9  CloudMonitoringOptions,
10  CloudMonitoringQuery,
11  CloudMonitoringVariableQuery,
12  MetricDescriptor,
13  MetricFindQueryTypes,
14  VariableQueryData,
15} from '../types';
16import CloudMonitoringDatasource from '../datasource';
17
18export type Props = QueryEditorProps<
19  CloudMonitoringDatasource,
20  CloudMonitoringQuery,
21  CloudMonitoringOptions,
22  CloudMonitoringVariableQuery
23>;
24
25export class CloudMonitoringVariableQueryEditor extends PureComponent<Props, VariableQueryData> {
26  queryTypes: Array<{ value: string; label: string }> = [
27    { value: MetricFindQueryTypes.Projects, label: 'Projects' },
28    { value: MetricFindQueryTypes.Services, label: 'Services' },
29    { value: MetricFindQueryTypes.MetricTypes, label: 'Metric Types' },
30    { value: MetricFindQueryTypes.LabelKeys, label: 'Label Keys' },
31    { value: MetricFindQueryTypes.LabelValues, label: 'Label Values' },
32    { value: MetricFindQueryTypes.ResourceTypes, label: 'Resource Types' },
33    { value: MetricFindQueryTypes.Aggregations, label: 'Aggregations' },
34    { value: MetricFindQueryTypes.Aligners, label: 'Aligners' },
35    { value: MetricFindQueryTypes.AlignmentPeriods, label: 'Alignment Periods' },
36    { value: MetricFindQueryTypes.Selectors, label: 'Selectors' },
37    { value: MetricFindQueryTypes.SLOServices, label: 'SLO Services' },
38    { value: MetricFindQueryTypes.SLO, label: 'Service Level Objectives (SLO)' },
39  ];
40
41  defaults: VariableQueryData = {
42    selectedQueryType: this.queryTypes[0].value,
43    metricDescriptors: [],
44    selectedService: '',
45    selectedMetricType: '',
46    labels: [],
47    labelKey: '',
48    metricTypes: [],
49    services: [],
50    sloServices: [],
51    selectedSLOService: '',
52    projects: [],
53    projectName: '',
54    loading: true,
55  };
56
57  constructor(props: Props) {
58    super(props);
59    this.state = Object.assign(
60      this.defaults,
61      { projectName: this.props.datasource.getDefaultProject() },
62      this.props.query
63    );
64  }
65
66  async componentDidMount() {
67    const projects = (await this.props.datasource.getProjects()) as MetricDescriptor[];
68    const metricDescriptors = await this.props.datasource.getMetricTypes(
69      this.props.query.projectName || this.props.datasource.getDefaultProject()
70    );
71    const services = extractServicesFromMetricDescriptors(metricDescriptors).map((m: any) => ({
72      value: m.service,
73      label: m.serviceShortName,
74    }));
75
76    let selectedService = '';
77    if (services.some((s) => s.value === getTemplateSrv().replace(this.state.selectedService))) {
78      selectedService = this.state.selectedService;
79    } else if (services && services.length > 0) {
80      selectedService = services[0].value;
81    }
82
83    const { metricTypes, selectedMetricType } = getMetricTypes(
84      metricDescriptors,
85      this.state.selectedMetricType,
86      getTemplateSrv().replace(this.state.selectedMetricType),
87      getTemplateSrv().replace(selectedService)
88    );
89
90    const sloServices = await this.props.datasource.getSLOServices(this.state.projectName);
91
92    const state: any = {
93      services,
94      selectedService,
95      metricTypes,
96      selectedMetricType,
97      metricDescriptors,
98      projects,
99      ...(await this.getLabels(selectedMetricType, this.state.projectName)),
100      sloServices,
101      loading: false,
102    };
103    this.setState(state, () => this.onPropsChange());
104  }
105
106  onPropsChange = () => {
107    const { metricDescriptors, labels, metricTypes, services, ...queryModel } = this.state;
108    this.props.onChange({ ...queryModel, refId: 'CloudMonitoringVariableQueryEditor-VariableQuery' });
109  };
110
111  async onQueryTypeChange(queryType: string) {
112    const state: any = {
113      selectedQueryType: queryType,
114      ...(await this.getLabels(this.state.selectedMetricType, this.state.projectName, queryType)),
115    };
116
117    this.setState(state);
118  }
119
120  async onProjectChange(projectName: string) {
121    const metricDescriptors = await this.props.datasource.getMetricTypes(projectName);
122    const labels = await this.getLabels(this.state.selectedMetricType, projectName);
123    const { metricTypes, selectedMetricType } = getMetricTypes(
124      metricDescriptors,
125      this.state.selectedMetricType,
126      getTemplateSrv().replace(this.state.selectedMetricType),
127      getTemplateSrv().replace(this.state.selectedService)
128    );
129
130    const sloServices = await this.props.datasource.getSLOServices(projectName);
131
132    this.setState(
133      {
134        ...labels,
135        metricTypes,
136        selectedMetricType,
137        metricDescriptors,
138        projectName,
139        sloServices,
140      },
141      () => this.onPropsChange()
142    );
143  }
144
145  async onServiceChange(service: string) {
146    const { metricTypes, selectedMetricType } = getMetricTypes(
147      this.state.metricDescriptors,
148      this.state.selectedMetricType,
149      getTemplateSrv().replace(this.state.selectedMetricType),
150      getTemplateSrv().replace(service)
151    );
152    const state: any = {
153      selectedService: service,
154      metricTypes,
155      selectedMetricType,
156      ...(await this.getLabels(selectedMetricType, this.state.projectName)),
157    };
158    this.setState(state, () => this.onPropsChange());
159  }
160
161  async onMetricTypeChange(metricType: string) {
162    const state: any = {
163      selectedMetricType: metricType,
164      ...(await this.getLabels(metricType, this.state.projectName)),
165    };
166    this.setState(state, () => this.onPropsChange());
167  }
168
169  onLabelKeyChange(labelKey: string) {
170    this.setState({ labelKey }, () => this.onPropsChange());
171  }
172
173  componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<VariableQueryData>) {
174    const selecQueryTypeChanged = prevState.selectedQueryType !== this.state.selectedQueryType;
175    const selectSLOServiceChanged = this.state.selectedSLOService !== prevState.selectedSLOService;
176    if (selecQueryTypeChanged || selectSLOServiceChanged) {
177      this.onPropsChange();
178    }
179  }
180
181  async getLabels(selectedMetricType: string, projectName: string, selectedQueryType = this.state.selectedQueryType) {
182    let result = { labels: this.state.labels, labelKey: this.state.labelKey };
183    if (selectedMetricType && selectedQueryType === MetricFindQueryTypes.LabelValues) {
184      const labels = await getLabelKeys(this.props.datasource, selectedMetricType, projectName);
185      const labelKey = labels.some((l) => l === getTemplateSrv().replace(this.state.labelKey))
186        ? this.state.labelKey
187        : labels[0];
188      result = { labels, labelKey };
189    }
190    return result;
191  }
192
193  renderQueryTypeSwitch(queryType: string) {
194    const variableOptionGroup = {
195      label: 'Template Variables',
196      expanded: false,
197      options: getTemplateSrv()
198        .getVariables()
199        .map((v: any) => ({
200          value: `$${v.name}`,
201          label: `$${v.name}`,
202        })),
203    };
204
205    switch (queryType) {
206      case MetricFindQueryTypes.MetricTypes:
207        return (
208          <>
209            <VariableQueryField
210              allowCustomValue={true}
211              value={this.state.projectName}
212              options={[variableOptionGroup, ...this.state.projects]}
213              onChange={(value) => this.onProjectChange(value)}
214              label="Project"
215            />
216            <VariableQueryField
217              value={this.state.selectedService}
218              options={[variableOptionGroup, ...this.state.services]}
219              onChange={(value) => this.onServiceChange(value)}
220              label="Service"
221            />
222          </>
223        );
224      case MetricFindQueryTypes.LabelKeys:
225      case MetricFindQueryTypes.LabelValues:
226      case MetricFindQueryTypes.ResourceTypes:
227        return (
228          <>
229            <VariableQueryField
230              allowCustomValue={true}
231              value={this.state.projectName}
232              options={[variableOptionGroup, ...this.state.projects]}
233              onChange={(value) => this.onProjectChange(value)}
234              label="Project"
235            />
236            <VariableQueryField
237              value={this.state.selectedService}
238              options={[variableOptionGroup, ...this.state.services]}
239              onChange={(value) => this.onServiceChange(value)}
240              label="Service"
241            />
242            <VariableQueryField
243              value={this.state.selectedMetricType}
244              options={[
245                variableOptionGroup,
246                ...this.state.metricTypes.map(({ value, name }) => ({ value, label: name })),
247              ]}
248              onChange={(value) => this.onMetricTypeChange(value)}
249              label="Metric Type"
250            />
251            {queryType === MetricFindQueryTypes.LabelValues && (
252              <VariableQueryField
253                value={this.state.labelKey}
254                options={[variableOptionGroup, ...this.state.labels.map((l) => ({ value: l, label: l }))]}
255                onChange={(value) => this.onLabelKeyChange(value)}
256                label="Label Key"
257              />
258            )}
259          </>
260        );
261      case MetricFindQueryTypes.Aligners:
262      case MetricFindQueryTypes.Aggregations:
263        return (
264          <>
265            <VariableQueryField
266              value={this.state.selectedService}
267              options={[variableOptionGroup, ...this.state.services]}
268              onChange={(value) => this.onServiceChange(value)}
269              label="Service"
270            />
271            <VariableQueryField
272              value={this.state.selectedMetricType}
273              options={[
274                variableOptionGroup,
275                ...this.state.metricTypes.map(({ value, name }) => ({ value, label: name })),
276              ]}
277              onChange={(value) => this.onMetricTypeChange(value)}
278              label="Metric Type"
279            />
280          </>
281        );
282      case MetricFindQueryTypes.SLOServices:
283        return (
284          <>
285            <VariableQueryField
286              allowCustomValue={true}
287              value={this.state.projectName}
288              options={[variableOptionGroup, ...this.state.projects]}
289              onChange={(value) => this.onProjectChange(value)}
290              label="Project"
291            />
292          </>
293        );
294
295      case MetricFindQueryTypes.SLO:
296        return (
297          <>
298            <VariableQueryField
299              allowCustomValue={true}
300              value={this.state.projectName}
301              options={[variableOptionGroup, ...this.state.projects]}
302              onChange={(value) => this.onProjectChange(value)}
303              label="Project"
304            />
305            <VariableQueryField
306              value={this.state.selectedSLOService}
307              options={[variableOptionGroup, ...this.state.sloServices]}
308              onChange={(value) => {
309                this.setState({
310                  ...this.state,
311                  selectedSLOService: value,
312                });
313              }}
314              label="SLO Service"
315            />
316          </>
317        );
318      default:
319        return '';
320    }
321  }
322
323  render() {
324    if (this.state.loading) {
325      return (
326        <div className="gf-form max-width-21">
327          <span className="gf-form-label width-10 query-keyword">Query Type</span>
328          <div className="gf-form-select-wrapper max-width-12">
329            <select className="gf-form-input">
330              <option>Loading...</option>
331            </select>
332          </div>
333        </div>
334      );
335    }
336
337    return (
338      <>
339        <VariableQueryField
340          value={this.state.selectedQueryType}
341          options={this.queryTypes}
342          onChange={(value) => this.onQueryTypeChange(value)}
343          label="Query Type"
344        />
345        {this.renderQueryTypeSwitch(this.state.selectedQueryType)}
346      </>
347    );
348  }
349}
350