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