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