2022-04-22 21:33:13 +08:00
import { css } from '@emotion/css' ;
2019-01-11 05:47:09 +08:00
import React , { PureComponent } from 'react' ;
2022-04-22 21:33:13 +08:00
import { Unsubscribable } from 'rxjs' ;
import {
2022-06-17 04:22:05 +08:00
CoreApp ,
2022-04-22 21:33:13 +08:00
DataQuery ,
DataSourceApi ,
DataSourceInstanceSettings ,
getDefaultTimeRange ,
LoadingState ,
PanelData ,
} from '@grafana/data' ;
import { selectors } from '@grafana/e2e-selectors' ;
import { DataSourcePicker , getDataSourceSrv } from '@grafana/runtime' ;
2022-04-29 02:07:36 +08:00
import { Button , CustomScrollbar , HorizontalGroup , InlineFormLabel , Modal , stylesFactory } from '@grafana/ui' ;
2018-12-19 17:53:14 +08:00
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp' ;
2022-04-22 21:33:13 +08:00
import config from 'app/core/config' ;
import { backendSrv } from 'app/core/services/backend_srv' ;
2021-11-16 01:15:13 +08:00
import { addQuery } from 'app/core/utils/query' ;
2021-10-13 23:49:40 +08:00
import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource' ;
2020-12-02 22:42:54 +08:00
import { DashboardQueryEditor , isSharedDashboardQuery } from 'app/plugins/datasource/dashboard' ;
2022-12-01 07:33:40 +08:00
import { QueryGroupDataSource , QueryGroupOptions } from 'app/types' ;
2022-04-22 21:33:13 +08:00
2022-12-01 07:33:40 +08:00
import { isQueryWithMixedDatasource } from '../../query-library/api/SavedQueriesApi' ;
import { getSavedQuerySrv } from '../../query-library/api/SavedQueriesSrv' ;
2022-04-22 21:33:13 +08:00
import { PanelQueryRunner } from '../state/PanelQueryRunner' ;
2021-11-16 01:15:13 +08:00
import { updateQueries } from '../state/updateQueries' ;
2018-11-20 21:17:03 +08:00
2022-04-22 21:33:13 +08:00
import { GroupActionComponents } from './QueryActionComponent' ;
import { QueryEditorRows } from './QueryEditorRows' ;
import { QueryGroupOptionsEditor } from './QueryGroupOptions' ;
2022-12-01 07:33:40 +08:00
import { SavedQueryPicker } from './SavedQueryPicker' ;
2022-04-22 21:33:13 +08:00
2018-06-27 03:07:41 +08:00
interface Props {
2020-12-02 22:42:54 +08:00
queryRunner : PanelQueryRunner ;
options : QueryGroupOptions ;
onOpenQueryInspector ? : ( ) = > void ;
onRunQueries : ( ) = > void ;
onOptionsChange : ( options : QueryGroupOptions ) = > void ;
2018-06-27 03:07:41 +08:00
}
2018-11-19 21:35:16 +08:00
interface State {
2020-05-05 21:48:31 +08:00
dataSource? : DataSourceApi ;
2020-12-04 21:24:55 +08:00
dsSettings? : DataSourceInstanceSettings ;
2021-10-13 23:49:40 +08:00
queries : DataQuery [ ] ;
2020-07-09 21:16:35 +08:00
helpContent : React.ReactNode ;
2018-12-12 13:52:08 +08:00
isLoadingHelp : boolean ;
2018-12-12 00:03:38 +08:00
isPickerOpen : boolean ;
2018-12-12 15:33:49 +08:00
isAddingMixed : boolean ;
2019-04-18 14:22:14 +08:00
data : PanelData ;
2020-04-27 03:59:14 +08:00
isHelpOpen : boolean ;
2021-04-21 19:57:17 +08:00
defaultDataSource? : DataSourceApi ;
2022-04-29 02:07:36 +08:00
scrollElement? : HTMLDivElement ;
2022-12-01 07:33:40 +08:00
savedQueryUid? : string | null ;
initialState : {
queries : DataQuery [ ] ;
dataSource? : QueryGroupDataSource ;
savedQueryUid? : string | null ;
} ;
2018-11-19 21:35:16 +08:00
}
2020-12-02 22:42:54 +08:00
export class QueryGroup extends PureComponent < Props , State > {
2020-01-21 17:08:07 +08:00
backendSrv = backendSrv ;
2020-12-04 21:24:55 +08:00
dataSourceSrv = getDataSourceSrv ( ) ;
2021-04-28 20:40:01 +08:00
querySubscription : Unsubscribable | null = null ;
2018-06-27 03:07:41 +08:00
2019-01-15 18:40:12 +08:00
state : State = {
isLoadingHelp : false ,
helpContent : null ,
isPickerOpen : false ,
isAddingMixed : false ,
2020-04-27 03:59:14 +08:00
isHelpOpen : false ,
2021-10-13 23:49:40 +08:00
queries : [ ] ,
2022-12-01 07:33:40 +08:00
savedQueryUid : null ,
initialState : {
queries : [ ] ,
savedQueryUid : null ,
} ,
2019-04-18 14:22:14 +08:00
data : {
state : LoadingState.NotStarted ,
series : [ ] ,
2021-01-07 18:26:56 +08:00
timeRange : getDefaultTimeRange ( ) ,
2019-04-18 14:22:14 +08:00
} ,
} ;
2020-05-05 21:48:31 +08:00
async componentDidMount() {
2022-12-01 07:33:40 +08:00
const { options , queryRunner } = this . props ;
2019-04-18 14:22:14 +08:00
2020-05-25 20:05:43 +08:00
this . querySubscription = queryRunner . getData ( { withTransforms : false , withFieldConfig : false } ) . subscribe ( {
2019-09-12 23:28:46 +08:00
next : ( data : PanelData ) = > this . onPanelDataUpdate ( data ) ,
} ) ;
2020-05-05 21:48:31 +08:00
2020-05-12 14:03:52 +08:00
try {
2021-11-02 16:48:46 +08:00
const ds = await this . dataSourceSrv . get ( options . dataSource ) ;
const dsSettings = this . dataSourceSrv . getInstanceSettings ( options . dataSource ) ;
2021-04-21 19:57:17 +08:00
const defaultDataSource = await this . dataSourceSrv . get ( ) ;
2021-11-02 16:48:46 +08:00
const datasource = ds . getRef ( ) ;
2021-10-30 01:57:24 +08:00
const queries = options . queries . map ( ( q ) = > ( q . datasource ? q : { . . . q , datasource } ) ) ;
2022-12-01 07:33:40 +08:00
this . setState ( {
queries ,
dataSource : ds ,
dsSettings ,
defaultDataSource ,
savedQueryUid : options.savedQueryUid ,
initialState : {
queries : options.queries.map ( ( q ) = > ( { . . . q } ) ) ,
dataSource : { . . . options . dataSource } ,
savedQueryUid : options.savedQueryUid ,
} ,
} ) ;
2020-05-12 14:03:52 +08:00
} catch ( error ) {
2020-12-04 21:24:55 +08:00
console . log ( 'failed to load data source' , error ) ;
2020-05-12 14:03:52 +08:00
}
2019-04-18 14:22:14 +08:00
}
componentWillUnmount() {
if ( this . querySubscription ) {
this . querySubscription . unsubscribe ( ) ;
this . querySubscription = null ;
}
}
2019-09-12 23:28:46 +08:00
onPanelDataUpdate ( data : PanelData ) {
this . setState ( { data } ) ;
}
2018-06-27 03:07:41 +08:00
2020-12-04 21:24:55 +08:00
onChangeDataSource = async ( newSettings : DataSourceInstanceSettings ) = > {
const { dsSettings } = this . state ;
2022-04-12 19:52:55 +08:00
const currentDS = dsSettings ? await getDataSourceSrv ( ) . get ( dsSettings . uid ) : undefined ;
const nextDS = await getDataSourceSrv ( ) . get ( newSettings . uid ) ;
2022-05-24 19:14:56 +08:00
// We need to pass in newSettings.uid as well here as that can be a variable expression and we want to store that in the query model not the current ds variable value
const queries = await updateQueries ( nextDS , newSettings . uid , this . state . queries , currentDS ) ;
2018-11-19 21:35:16 +08:00
2020-12-04 21:24:55 +08:00
const dataSource = await this . dataSourceSrv . get ( newSettings . name ) ;
this . onChange ( {
queries ,
2022-12-01 07:33:40 +08:00
savedQueryUid : null ,
2020-12-04 21:24:55 +08:00
dataSource : {
name : newSettings.name ,
uid : newSettings.uid ,
2021-10-30 01:57:24 +08:00
type : newSettings . meta . id ,
2020-12-04 21:24:55 +08:00
default : newSettings . isDefault ,
} ,
} ) ;
2020-04-27 03:59:14 +08:00
2020-12-02 22:42:54 +08:00
this . setState ( {
2021-10-13 23:49:40 +08:00
queries ,
2022-12-01 07:33:40 +08:00
savedQueryUid : null ,
2020-12-02 22:42:54 +08:00
dataSource : dataSource ,
2020-12-04 21:24:55 +08:00
dsSettings : newSettings ,
2020-04-15 22:51:51 +08:00
} ) ;
2018-11-22 22:53:34 +08:00
} ;
2022-12-01 07:33:40 +08:00
onChangeSavedQuery = async ( savedQueryUid : string | null ) = > {
if ( ! savedQueryUid ? . length ) {
// leave the queries, remove the link
this . onChange ( {
queries : this.state.queries ,
savedQueryUid : null ,
dataSource : {
name : this.state.dsSettings?.name ,
uid : this.state.dsSettings?.uid ,
type : this . state . dsSettings ? . meta . id ,
default : this . state . dsSettings ? . isDefault ,
} ,
} ) ;
this . setState ( {
queries : this.state.queries ,
savedQueryUid : null ,
dataSource : this.state.dataSource ,
dsSettings : this.state.dsSettings ,
} ) ;
return ;
}
const { dsSettings } = this . state ;
const currentDS = dsSettings ? await getDataSourceSrv ( ) . get ( dsSettings . uid ) : undefined ;
const resp = await getSavedQuerySrv ( ) . getSavedQueries ( [ { uid : savedQueryUid } ] ) ;
if ( ! resp ? . length ) {
throw new Error ( 'TODO error handling' ) ;
}
const savedQuery = resp [ 0 ] ;
const isMixedDatasource = isQueryWithMixedDatasource ( savedQuery ) ;
const nextDS = isMixedDatasource
? await getDataSourceSrv ( ) . get ( '-- Mixed --' )
: await getDataSourceSrv ( ) . get ( savedQuery . queries [ 0 ] . datasource ? . uid ) ;
// We need to pass in newSettings.uid as well here as that can be a variable expression and we want to store that in the query model not the current ds variable value
const queries = await updateQueries ( nextDS , nextDS . uid , savedQuery . queries , currentDS ) ;
const newDsSettings = await getDataSourceSrv ( ) . getInstanceSettings ( nextDS . uid ) ;
if ( ! newDsSettings ) {
throw new Error ( 'TODO error handling' ) ;
}
this . onChange ( {
queries ,
savedQueryUid : savedQueryUid ,
dataSource : {
name : newDsSettings.name ,
uid : newDsSettings.uid ,
type : newDsSettings . meta . id ,
default : newDsSettings . isDefault ,
} ,
} ) ;
this . setState ( {
queries ,
savedQueryUid ,
dataSource : nextDS ,
dsSettings : newDsSettings ,
} ) ;
} ;
2018-12-11 20:36:44 +08:00
onAddQueryClick = ( ) = > {
2021-10-13 23:49:40 +08:00
const { queries } = this . state ;
this . onQueriesChange ( addQuery ( queries , this . newQuery ( ) ) ) ;
2019-10-18 19:09:55 +08:00
this . onScrollBottom ( ) ;
2018-12-11 20:36:44 +08:00
} ;
2021-04-21 19:57:17 +08:00
newQuery ( ) : Partial < DataQuery > {
const { dsSettings , defaultDataSource } = this . state ;
2021-10-30 01:57:24 +08:00
const ds = ! dsSettings ? . meta . mixed ? dsSettings : defaultDataSource ;
2021-04-21 19:57:17 +08:00
return {
2022-06-17 04:22:05 +08:00
. . . this . state . dataSource ? . getDefaultQuery ? . ( CoreApp . PanelEditor ) ,
2021-10-30 01:57:24 +08:00
datasource : { uid : ds?.uid , type : ds ? . type } ,
2021-04-21 19:57:17 +08:00
} ;
}
2020-12-04 21:24:55 +08:00
onChange ( changedProps : Partial < QueryGroupOptions > ) {
this . props . onOptionsChange ( {
. . . this . props . options ,
. . . changedProps ,
} ) ;
}
2019-10-31 02:38:28 +08:00
onAddExpressionClick = ( ) = > {
2021-10-13 23:49:40 +08:00
this . onQueriesChange ( addQuery ( this . state . queries , expressionDatasource . newQuery ( ) ) ) ;
2019-10-31 02:38:28 +08:00
this . onScrollBottom ( ) ;
} ;
2019-10-18 19:09:55 +08:00
onScrollBottom = ( ) = > {
2022-04-29 02:07:36 +08:00
setTimeout ( ( ) = > {
if ( this . state . scrollElement ) {
this . state . scrollElement . scrollTo ( { top : 10000 } ) ;
}
} , 20 ) ;
2018-12-11 20:36:44 +08:00
} ;
2020-12-16 14:24:56 +08:00
onUpdateAndRun = ( options : QueryGroupOptions ) = > {
this . props . onOptionsChange ( options ) ;
this . props . onRunQueries ( ) ;
} ;
2021-01-20 03:18:06 +08:00
renderTopSection ( styles : QueriesTabStyles ) {
2020-12-16 14:24:56 +08:00
const { onOpenQueryInspector , options } = this . props ;
2020-12-04 21:24:55 +08:00
const { dataSource , data } = this . state ;
2018-11-09 22:49:55 +08:00
2019-01-17 18:25:44 +08:00
return (
2020-04-27 03:59:14 +08:00
< div >
< div className = { styles . dataSourceRow } >
2021-10-12 20:26:01 +08:00
< InlineFormLabel htmlFor = "data-source-picker" width = { 'auto' } >
Data source
< / InlineFormLabel >
2020-04-27 03:59:14 +08:00
< div className = { styles . dataSourceRowItem } >
2020-12-04 21:24:55 +08:00
< DataSourcePicker
onChange = { this . onChangeDataSource }
2021-10-30 01:57:24 +08:00
current = { options . dataSource }
2020-12-04 21:24:55 +08:00
metrics = { true }
mixed = { true }
dashboard = { true }
variables = { true }
2020-04-28 00:29:41 +08:00
/ >
< / div >
2020-12-04 21:24:55 +08:00
{ dataSource && (
< >
< div className = { styles . dataSourceRowItem } >
< Button
variant = "secondary"
icon = "question-circle"
title = "Open data source help"
onClick = { this . onOpenHelp }
/ >
< / div >
< div className = { styles . dataSourceRowItemOptions } >
< QueryGroupOptionsEditor
options = { options }
dataSource = { dataSource }
data = { data }
2020-12-16 14:24:56 +08:00
onChange = { this . onUpdateAndRun }
2020-12-04 21:24:55 +08:00
/ >
< / div >
{ onOpenQueryInspector && (
< div className = { styles . dataSourceRowItem } >
< Button
variant = "secondary"
onClick = { onOpenQueryInspector }
aria - label = { selectors . components . QueryTab . queryInspectorButton }
>
Query inspector
< / Button >
< / div >
) }
< / >
2020-12-02 22:42:54 +08:00
) }
2020-04-27 03:59:14 +08:00
< / div >
2022-12-01 07:33:40 +08:00
{ config . featureToggles . queryLibrary && (
< >
< div className = { styles . dataSourceRow } >
< InlineFormLabel htmlFor = "saved-query-picker" width = { 'auto' } >
Saved query
< / InlineFormLabel >
< div className = { styles . dataSourceRowItem } >
< SavedQueryPicker current = { this . state . savedQueryUid } onChange = { this . onChangeSavedQuery } / >
< / div >
< / div >
< / >
) }
2020-04-27 03:59:14 +08:00
< / div >
2019-01-17 18:25:44 +08:00
) ;
2020-04-27 03:59:14 +08:00
}
onOpenHelp = ( ) = > {
this . setState ( { isHelpOpen : true } ) ;
} ;
onCloseHelp = ( ) = > {
this . setState ( { isHelpOpen : false } ) ;
2018-12-12 00:03:38 +08:00
} ;
2018-11-09 22:49:55 +08:00
2018-12-12 15:33:49 +08:00
renderMixedPicker = ( ) = > {
return (
< DataSourcePicker
2020-12-04 21:24:55 +08:00
mixed = { false }
2018-12-14 21:05:47 +08:00
onChange = { this . onAddMixedQuery }
2018-12-12 15:33:49 +08:00
current = { null }
autoFocus = { true }
2021-01-08 22:30:57 +08:00
variables = { true }
2018-12-12 15:33:49 +08:00
onBlur = { this . onMixedPickerBlur }
2019-04-09 20:59:30 +08:00
openMenuOnFocus = { true }
2018-12-12 15:33:49 +08:00
/ >
) ;
} ;
2019-07-30 21:49:32 +08:00
onAddMixedQuery = ( datasource : any ) = > {
2020-12-02 22:42:54 +08:00
this . onAddQuery ( { datasource : datasource.name } ) ;
2022-04-29 02:07:36 +08:00
this . setState ( { isAddingMixed : false } ) ;
2018-12-12 15:33:49 +08:00
} ;
onMixedPickerBlur = ( ) = > {
this . setState ( { isAddingMixed : false } ) ;
} ;
2020-12-02 22:42:54 +08:00
onAddQuery = ( query : Partial < DataQuery > ) = > {
2021-10-13 23:49:40 +08:00
const { dsSettings , queries } = this . state ;
2021-10-30 01:57:24 +08:00
this . onQueriesChange ( addQuery ( queries , query , { type : dsSettings ? . type , uid : dsSettings?.uid } ) ) ;
2020-12-02 22:42:54 +08:00
this . onScrollBottom ( ) ;
2019-01-29 16:39:23 +08:00
} ;
2020-12-04 21:24:55 +08:00
onQueriesChange = ( queries : DataQuery [ ] ) = > {
this . onChange ( { queries } ) ;
2021-10-13 23:49:40 +08:00
this . setState ( { queries } ) ;
2020-12-04 21:24:55 +08:00
} ;
renderQueries ( dsSettings : DataSourceInstanceSettings ) {
2021-10-13 23:49:40 +08:00
const { onRunQueries } = this . props ;
const { data , queries } = this . state ;
2019-10-18 19:09:55 +08:00
2020-12-04 21:24:55 +08:00
if ( isSharedDashboardQuery ( dsSettings . name ) ) {
2021-03-29 14:16:24 +08:00
return (
< DashboardQueryEditor
2021-10-13 23:49:40 +08:00
queries = { queries }
2021-03-29 14:16:24 +08:00
panelData = { data }
onChange = { this . onQueriesChange }
onRunQueries = { onRunQueries }
/ >
) ;
2019-10-18 19:09:55 +08:00
}
return (
2020-04-27 15:09:05 +08:00
< div aria - label = { selectors . components . QueryTab . content } >
2019-10-18 19:09:55 +08:00
< QueryEditorRows
2021-10-13 23:49:40 +08:00
queries = { queries }
2020-12-04 21:24:55 +08:00
dsSettings = { dsSettings }
onQueriesChange = { this . onQueriesChange }
2020-12-02 22:42:54 +08:00
onAddQuery = { this . onAddQuery }
onRunQueries = { onRunQueries }
2019-10-18 19:09:55 +08:00
data = { data }
/ >
2020-04-24 18:51:38 +08:00
< / div >
2019-10-18 19:09:55 +08:00
) ;
2020-04-27 03:59:14 +08:00
}
2021-01-20 03:18:06 +08:00
isExpressionsSupported ( dsSettings : DataSourceInstanceSettings ) : boolean {
return ( dsSettings . meta . alerting || dsSettings . meta . mixed ) === true ;
}
2021-09-20 22:36:04 +08:00
renderExtraActions() {
2021-11-17 19:05:39 +08:00
return GroupActionComponents . getAllExtraRenderAction ( )
. map ( ( action , index ) = >
action ( {
onAddQuery : this.onAddQuery ,
onChangeDataSource : this.onChangeDataSource ,
key : index ,
} )
)
. filter ( Boolean ) ;
2021-09-20 22:36:04 +08:00
}
2021-01-20 03:18:06 +08:00
renderAddQueryRow ( dsSettings : DataSourceInstanceSettings , styles : QueriesTabStyles ) {
2020-12-04 21:24:55 +08:00
const { isAddingMixed } = this . state ;
const showAddButton = ! ( isAddingMixed || isSharedDashboardQuery ( dsSettings . name ) ) ;
2020-04-27 03:59:14 +08:00
return (
< HorizontalGroup spacing = "md" align = "flex-start" >
{ showAddButton && (
2020-04-30 15:26:59 +08:00
< Button
icon = "plus"
onClick = { this . onAddQueryClick }
variant = "secondary"
aria - label = { selectors . components . QueryTab . addQuery }
>
2020-04-27 03:59:14 +08:00
Query
< / Button >
) }
2021-01-23 01:27:33 +08:00
{ config . expressionsEnabled && this . isExpressionsSupported ( dsSettings ) && (
2022-01-20 21:52:36 +08:00
< Button
icon = "plus"
onClick = { this . onAddExpressionClick }
variant = "secondary"
className = { styles . expressionButton }
>
< span > Expression & nbsp ; < / span >
< / Button >
2020-04-27 03:59:14 +08:00
) }
2021-09-20 22:36:04 +08:00
{ this . renderExtraActions ( ) }
2020-04-27 03:59:14 +08:00
< / HorizontalGroup >
) ;
}
2019-10-18 19:09:55 +08:00
2022-04-29 02:07:36 +08:00
setScrollRef = ( scrollElement : HTMLDivElement ) : void = > {
this . setState ( { scrollElement } ) ;
} ;
2019-10-18 19:09:55 +08:00
render() {
2022-04-29 02:07:36 +08:00
const { isHelpOpen , dsSettings } = this . state ;
2020-04-27 03:59:14 +08:00
const styles = getStyles ( ) ;
2018-12-12 13:52:08 +08:00
2018-11-09 20:17:41 +08:00
return (
2022-04-29 02:07:36 +08:00
< CustomScrollbar autoHeightMin = "100%" scrollRefCallback = { this . setScrollRef } >
2020-04-27 03:59:14 +08:00
< div className = { styles . innerWrapper } >
{ this . renderTopSection ( styles ) }
2020-12-04 21:24:55 +08:00
{ dsSettings && (
< >
< div className = { styles . queriesWrapper } > { this . renderQueries ( dsSettings ) } < / div >
2021-01-20 03:18:06 +08:00
{ this . renderAddQueryRow ( dsSettings , styles ) }
2020-12-04 21:24:55 +08:00
{ isHelpOpen && (
< Modal title = "Data source help" isOpen = { true } onDismiss = { this . onCloseHelp } >
< PluginHelp plugin = { dsSettings . meta } type = "query_help" / >
< / Modal >
) }
< / >
2020-04-27 03:59:14 +08:00
) }
< / div >
< / CustomScrollbar >
2018-11-09 20:17:41 +08:00
) ;
2018-06-27 03:07:41 +08:00
}
}
2020-04-27 03:59:14 +08:00
const getStyles = stylesFactory ( ( ) = > {
const { theme } = config ;
return {
innerWrapper : css `
display : flex ;
flex - direction : column ;
padding : $ { theme . spacing . md } ;
` ,
dataSourceRow : css `
display : flex ;
margin - bottom : $ { theme . spacing . md } ;
` ,
dataSourceRowItem : css `
margin - right : $ { theme . spacing . inlineFormMargin } ;
` ,
dataSourceRowItemOptions : css `
flex - grow : 1 ;
2020-04-28 00:29:41 +08:00
margin - right : $ { theme . spacing . inlineFormMargin } ;
2020-04-27 03:59:14 +08:00
` ,
queriesWrapper : css `
padding - bottom : 16px ;
` ,
2021-01-20 03:18:06 +08:00
expressionWrapper : css ` ` ,
expressionButton : css `
margin - right : $ { theme . spacing . sm } ;
` ,
2020-04-27 03:59:14 +08:00
} ;
} ) ;
2021-01-20 03:18:06 +08:00
type QueriesTabStyles = ReturnType < typeof getStyles > ;