2022-04-22 14:33:13 +01:00
import { css } from '@emotion/css' ;
2019-01-10 21:47:09 +00:00
import React , { PureComponent } from 'react' ;
2022-04-22 14:33:13 +01:00
import { Unsubscribable } from 'rxjs' ;
import {
2022-06-16 22:22:05 +02:00
CoreApp ,
2022-04-22 14:33:13 +01:00
DataSourceApi ,
DataSourceInstanceSettings ,
getDefaultTimeRange ,
LoadingState ,
PanelData ,
2023-07-25 14:35:07 +02:00
PluginType ,
2022-04-22 14:33:13 +01:00
} from '@grafana/data' ;
import { selectors } from '@grafana/e2e-selectors' ;
2023-04-20 13:44:19 +02:00
import { getDataSourceSrv , locationService } from '@grafana/runtime' ;
2023-06-21 10:55:55 +02:00
import { DataQuery } from '@grafana/schema' ;
2022-04-28 20:07:36 +02:00
import { Button , CustomScrollbar , HorizontalGroup , InlineFormLabel , Modal , stylesFactory } from '@grafana/ui' ;
2018-12-19 10:53:14 +01:00
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp' ;
2022-04-22 14:33:13 +01:00
import config from 'app/core/config' ;
import { backendSrv } from 'app/core/services/backend_srv' ;
2022-12-05 15:44:04 +01:00
import { addQuery , queryIsEmpty } from 'app/core/utils/query' ;
2023-04-20 13:44:19 +02:00
import { DataSourceModal } from 'app/features/datasources/components/picker/DataSourceModal' ;
2023-04-19 15:08:09 +02:00
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker' ;
2021-10-13 09:49:40 -06:00
import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource' ;
2023-07-25 14:35:07 +02:00
import { AngularDeprecationPluginNotice } from 'app/features/plugins/angularDeprecation/AngularDeprecationPluginNotice' ;
2020-12-02 15:42:54 +01:00
import { DashboardQueryEditor , isSharedDashboardQuery } from 'app/plugins/datasource/dashboard' ;
2023-06-21 10:55:55 +02:00
import { GrafanaQuery } from 'app/plugins/datasource/grafana/types' ;
2023-04-21 15:32:47 +02:00
import { QueryGroupOptions } from 'app/types' ;
2022-04-22 14:33:13 +01:00
2023-07-25 14:35:07 +02:00
import { isAngularDatasourcePlugin } from '../../plugins/angularDeprecation/utils' ;
2022-04-22 14:33:13 +01:00
import { PanelQueryRunner } from '../state/PanelQueryRunner' ;
2021-11-15 18:15:13 +01:00
import { updateQueries } from '../state/updateQueries' ;
2018-11-20 14:17:03 +01:00
2022-04-22 14:33:13 +01:00
import { GroupActionComponents } from './QueryActionComponent' ;
import { QueryEditorRows } from './QueryEditorRows' ;
import { QueryGroupOptionsEditor } from './QueryGroupOptions' ;
2023-02-22 12:26:41 -03:00
export interface Props {
2020-12-02 15:42:54 +01:00
queryRunner : PanelQueryRunner ;
options : QueryGroupOptions ;
onOpenQueryInspector ? : ( ) = > void ;
onRunQueries : ( ) = > void ;
onOptionsChange : ( options : QueryGroupOptions ) = > void ;
2018-06-26 12:07:41 -07:00
}
2018-11-19 14:35:16 +01:00
interface State {
2020-05-05 15:48:31 +02:00
dataSource? : DataSourceApi ;
2020-12-04 14:24:55 +01:00
dsSettings? : DataSourceInstanceSettings ;
2021-10-13 09:49:40 -06:00
queries : DataQuery [ ] ;
2020-07-09 15:16:35 +02:00
helpContent : React.ReactNode ;
2018-12-12 06:52:08 +01:00
isLoadingHelp : boolean ;
2018-12-11 17:03:38 +01:00
isPickerOpen : boolean ;
2023-04-20 13:44:19 +02:00
isDataSourceModalOpen : boolean ;
2019-04-17 23:22:14 -07:00
data : PanelData ;
2020-04-26 21:59:14 +02:00
isHelpOpen : boolean ;
2021-04-21 13:57:17 +02:00
defaultDataSource? : DataSourceApi ;
2022-04-28 20:07:36 +02:00
scrollElement? : HTMLDivElement ;
2018-11-19 14:35:16 +01:00
}
2020-12-02 15:42:54 +01:00
export class QueryGroup extends PureComponent < Props , State > {
2020-01-21 09:08:07 +00:00
backendSrv = backendSrv ;
2020-12-04 14:24:55 +01:00
dataSourceSrv = getDataSourceSrv ( ) ;
2021-04-28 15:40:01 +03:00
querySubscription : Unsubscribable | null = null ;
2018-06-26 12:07:41 -07:00
2019-01-15 11:40:12 +01:00
state : State = {
2023-05-10 18:57:10 +02:00
isDataSourceModalOpen : ! ! locationService . getSearchObject ( ) . firstPanel ,
2019-01-15 11:40:12 +01:00
isLoadingHelp : false ,
helpContent : null ,
isPickerOpen : false ,
2020-04-26 21:59:14 +02:00
isHelpOpen : false ,
2021-10-13 09:49:40 -06:00
queries : [ ] ,
2019-04-17 23:22:14 -07:00
data : {
state : LoadingState.NotStarted ,
series : [ ] ,
2021-01-07 11:26:56 +01:00
timeRange : getDefaultTimeRange ( ) ,
2019-04-17 23:22:14 -07:00
} ,
} ;
2020-05-05 15:48:31 +02:00
async componentDidMount() {
2022-12-01 03:33:40 +04:00
const { options , queryRunner } = this . props ;
2019-04-17 23:22:14 -07:00
2020-05-25 14:05:43 +02:00
this . querySubscription = queryRunner . getData ( { withTransforms : false , withFieldConfig : false } ) . subscribe ( {
2019-09-12 17:28:46 +02:00
next : ( data : PanelData ) = > this . onPanelDataUpdate ( data ) ,
} ) ;
2020-05-05 15:48:31 +02:00
2023-04-24 17:46:31 +03:00
this . setNewQueriesAndDatasource ( options ) ;
2023-05-10 18:57:10 +02:00
// Clean up the first panel flag since the modal is now open
if ( ! ! locationService . getSearchObject ( ) . firstPanel ) {
locationService . partial ( { firstPanel : null } , true ) ;
}
2023-04-24 17:46:31 +03:00
}
componentWillUnmount() {
if ( this . querySubscription ) {
this . querySubscription . unsubscribe ( ) ;
this . querySubscription = null ;
}
}
async componentDidUpdate() {
const { options } = this . props ;
2023-04-26 13:41:31 +02:00
const currentDS = await getDataSourceSrv ( ) . get ( options . dataSource ) ;
if ( this . state . dataSource && currentDS . uid !== this . state . dataSource ? . uid ) {
2023-04-24 17:46:31 +03:00
this . setNewQueriesAndDatasource ( options ) ;
}
}
async setNewQueriesAndDatasource ( options : QueryGroupOptions ) {
2020-05-12 08:03:52 +02:00
try {
2021-11-02 01:48:46 -07:00
const ds = await this . dataSourceSrv . get ( options . dataSource ) ;
const dsSettings = this . dataSourceSrv . getInstanceSettings ( options . dataSource ) ;
2023-02-22 12:26:41 -03:00
2021-04-21 13:57:17 +02:00
const defaultDataSource = await this . dataSourceSrv . get ( ) ;
2021-11-02 01:48:46 -07:00
const datasource = ds . getRef ( ) ;
2022-12-05 15:44:04 +01:00
const queries = options . queries . map ( ( q ) = > ( {
. . . ( queryIsEmpty ( q ) && ds ? . getDefaultQuery ? . ( CoreApp . PanelEditor ) ) ,
datasource ,
. . . q ,
} ) ) ;
2023-05-10 18:57:10 +02:00
2022-12-01 03:33:40 +04:00
this . setState ( {
queries ,
dataSource : ds ,
dsSettings ,
defaultDataSource ,
} ) ;
2020-05-12 08:03:52 +02:00
} catch ( error ) {
2020-12-04 14:24:55 +01:00
console . log ( 'failed to load data source' , error ) ;
2020-05-12 08:03:52 +02:00
}
2019-04-17 23:22:14 -07:00
}
2019-09-12 17:28:46 +02:00
onPanelDataUpdate ( data : PanelData ) {
this . setState ( { data } ) ;
}
2018-06-26 12:07:41 -07:00
2023-06-21 10:55:55 +02:00
onChangeDataSource = async (
newSettings : DataSourceInstanceSettings ,
defaultQueries? : DataQuery [ ] | GrafanaQuery [ ]
) = > {
2020-12-04 14:24:55 +01:00
const { dsSettings } = this . state ;
2022-04-12 13:52:55 +02:00
const currentDS = dsSettings ? await getDataSourceSrv ( ) . get ( dsSettings . uid ) : undefined ;
const nextDS = await getDataSourceSrv ( ) . get ( newSettings . uid ) ;
2022-05-24 13:14:56 +02: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
2023-06-21 10:55:55 +02:00
const queries = defaultQueries || ( await updateQueries ( nextDS , newSettings . uid , this . state . queries , currentDS ) ) ;
2018-11-19 14:35:16 +01:00
2020-12-04 14:24:55 +01:00
const dataSource = await this . dataSourceSrv . get ( newSettings . name ) ;
2023-06-06 10:28:52 -06:00
2020-12-04 14:24:55 +01:00
this . onChange ( {
queries ,
dataSource : {
name : newSettings.name ,
uid : newSettings.uid ,
2021-10-29 10:57:24 -07:00
type : newSettings . meta . id ,
2020-12-04 14:24:55 +01:00
default : newSettings . isDefault ,
} ,
} ) ;
2020-04-26 21:59:14 +02:00
2020-12-02 15:42:54 +01:00
this . setState ( {
2021-10-13 09:49:40 -06:00
queries ,
2020-12-02 15:42:54 +01:00
dataSource : dataSource ,
2020-12-04 14:24:55 +01:00
dsSettings : newSettings ,
2020-04-15 07:51:51 -07:00
} ) ;
2023-06-21 10:55:55 +02:00
if ( defaultQueries ) {
this . props . onRunQueries ( ) ;
}
2018-11-22 15:53:34 +01:00
} ;
2018-12-11 13:36:44 +01:00
onAddQueryClick = ( ) = > {
2021-10-13 09:49:40 -06:00
const { queries } = this . state ;
this . onQueriesChange ( addQuery ( queries , this . newQuery ( ) ) ) ;
2019-10-18 13:09:55 +02:00
this . onScrollBottom ( ) ;
2018-12-11 13:36:44 +01:00
} ;
2021-04-21 13:57:17 +02:00
newQuery ( ) : Partial < DataQuery > {
const { dsSettings , defaultDataSource } = this . state ;
2021-10-29 10:57:24 -07:00
const ds = ! dsSettings ? . meta . mixed ? dsSettings : defaultDataSource ;
2021-04-21 13:57:17 +02:00
return {
2022-06-16 22:22:05 +02:00
. . . this . state . dataSource ? . getDefaultQuery ? . ( CoreApp . PanelEditor ) ,
2021-10-29 10:57:24 -07:00
datasource : { uid : ds?.uid , type : ds ? . type } ,
2021-04-21 13:57:17 +02:00
} ;
}
2020-12-04 14:24:55 +01:00
onChange ( changedProps : Partial < QueryGroupOptions > ) {
this . props . onOptionsChange ( {
. . . this . props . options ,
. . . changedProps ,
} ) ;
}
2019-10-30 11:38:28 -07:00
onAddExpressionClick = ( ) = > {
2021-10-13 09:49:40 -06:00
this . onQueriesChange ( addQuery ( this . state . queries , expressionDatasource . newQuery ( ) ) ) ;
2019-10-30 11:38:28 -07:00
this . onScrollBottom ( ) ;
} ;
2019-10-18 13:09:55 +02:00
onScrollBottom = ( ) = > {
2022-04-28 20:07:36 +02:00
setTimeout ( ( ) = > {
if ( this . state . scrollElement ) {
this . state . scrollElement . scrollTo ( { top : 10000 } ) ;
}
} , 20 ) ;
2018-12-11 13:36:44 +01:00
} ;
2020-12-15 22:24:56 -08:00
onUpdateAndRun = ( options : QueryGroupOptions ) = > {
this . props . onOptionsChange ( options ) ;
this . props . onRunQueries ( ) ;
} ;
2021-01-19 14:18:06 -05:00
renderTopSection ( styles : QueriesTabStyles ) {
2020-12-15 22:24:56 -08:00
const { onOpenQueryInspector , options } = this . props ;
2020-12-04 14:24:55 +01:00
const { dataSource , data } = this . state ;
2018-11-09 15:49:55 +01:00
2019-01-17 11:25:44 +01:00
return (
2020-04-26 21:59:14 +02:00
< div >
< div className = { styles . dataSourceRow } >
2021-10-12 13:26:01 +01:00
< InlineFormLabel htmlFor = "data-source-picker" width = { 'auto' } >
Data source
< / InlineFormLabel >
2023-04-20 13:44:19 +02:00
< div className = { styles . dataSourceRowItem } > { this . renderDataSourcePickerWithPrompt ( ) } < / div >
2020-12-04 14:24:55 +01:00
{ dataSource && (
< >
< div className = { styles . dataSourceRowItem } >
< Button
variant = "secondary"
icon = "question-circle"
title = "Open data source help"
onClick = { this . onOpenHelp }
2023-02-22 12:26:41 -03:00
data - testid = "query-tab-help-button"
2020-12-04 14:24:55 +01:00
/ >
< / div >
< div className = { styles . dataSourceRowItemOptions } >
< QueryGroupOptionsEditor
options = { options }
dataSource = { dataSource }
data = { data }
2020-12-15 22:24:56 -08:00
onChange = { this . onUpdateAndRun }
2020-12-04 14:24:55 +01: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 15:42:54 +01:00
) }
2020-04-26 21:59:14 +02:00
< / div >
2023-07-25 14:35:07 +02:00
{ dataSource && isAngularDatasourcePlugin ( dataSource . uid ) && (
< AngularDeprecationPluginNotice
pluginId = { dataSource . type }
pluginType = { PluginType . datasource }
angularSupportEnabled = { config ? . angularSupportEnabled }
showPluginDetailsLink = { true }
/ >
) }
2020-04-26 21:59:14 +02:00
< / div >
2019-01-17 11:25:44 +01:00
) ;
2020-04-26 21:59:14 +02:00
}
onOpenHelp = ( ) = > {
this . setState ( { isHelpOpen : true } ) ;
} ;
onCloseHelp = ( ) = > {
this . setState ( { isHelpOpen : false } ) ;
2018-12-11 17:03:38 +01:00
} ;
2018-11-09 15:49:55 +01:00
2023-04-28 19:09:44 +02:00
onCloseDataSourceModal = ( ) = > {
this . setState ( { isDataSourceModalOpen : false } ) ;
} ;
2023-04-20 13:44:19 +02:00
renderDataSourcePickerWithPrompt = ( ) = > {
const { isDataSourceModalOpen } = this . state ;
const commonProps = {
2023-07-06 11:50:55 +02:00
metrics : true ,
mixed : true ,
dashboard : true ,
variables : true ,
2023-04-20 13:44:19 +02:00
current : this.props.options.dataSource ,
2023-07-06 11:50:55 +02:00
uploadFile : true ,
2023-06-21 10:55:55 +02:00
onChange : async ( ds : DataSourceInstanceSettings , defaultQueries? : DataQuery [ ] | GrafanaQuery [ ] ) = > {
await this . onChangeDataSource ( ds , defaultQueries ) ;
2023-04-28 19:09:44 +02:00
this . onCloseDataSourceModal ( ) ;
2023-04-20 13:44:19 +02:00
} ,
} ;
return (
< >
{ isDataSourceModalOpen && config . featureToggles . advancedDataSourcePicker && (
2023-04-28 19:09:44 +02:00
< DataSourceModal { ...commonProps } onDismiss = { this . onCloseDataSourceModal } > < / DataSourceModal >
2023-04-20 13:44:19 +02:00
) }
2023-07-06 11:50:55 +02:00
< DataSourcePicker { ...commonProps } / >
2023-04-20 13:44:19 +02:00
< / >
) ;
} ;
2020-12-02 15:42:54 +01:00
onAddQuery = ( query : Partial < DataQuery > ) = > {
2021-10-13 09:49:40 -06:00
const { dsSettings , queries } = this . state ;
2021-10-29 10:57:24 -07:00
this . onQueriesChange ( addQuery ( queries , query , { type : dsSettings ? . type , uid : dsSettings?.uid } ) ) ;
2020-12-02 15:42:54 +01:00
this . onScrollBottom ( ) ;
2019-01-29 09:39:23 +01:00
} ;
2023-03-01 11:26:19 +01:00
onQueriesChange = ( queries : DataQuery [ ] | GrafanaQuery [ ] ) = > {
2020-12-04 14:24:55 +01:00
this . onChange ( { queries } ) ;
2021-10-13 09:49:40 -06:00
this . setState ( { queries } ) ;
2020-12-04 14:24:55 +01:00
} ;
renderQueries ( dsSettings : DataSourceInstanceSettings ) {
2021-10-13 09:49:40 -06:00
const { onRunQueries } = this . props ;
const { data , queries } = this . state ;
2020-12-04 14:24:55 +01:00
if ( isSharedDashboardQuery ( dsSettings . name ) ) {
2021-03-29 08:16:24 +02:00
return (
< DashboardQueryEditor
2021-10-13 09:49:40 -06:00
queries = { queries }
2021-03-29 08:16:24 +02:00
panelData = { data }
onChange = { this . onQueriesChange }
onRunQueries = { onRunQueries }
/ >
) ;
2019-10-18 13:09:55 +02:00
}
return (
2020-04-27 09:09:05 +02:00
< div aria-label = { selectors . components . QueryTab . content } >
2019-10-18 13:09:55 +02:00
< QueryEditorRows
2021-10-13 09:49:40 -06:00
queries = { queries }
2020-12-04 14:24:55 +01:00
dsSettings = { dsSettings }
onQueriesChange = { this . onQueriesChange }
2020-12-02 15:42:54 +01:00
onAddQuery = { this . onAddQuery }
onRunQueries = { onRunQueries }
2019-10-18 13:09:55 +02:00
data = { data }
/ >
2020-04-24 12:51:38 +02:00
< / div >
2019-10-18 13:09:55 +02:00
) ;
2020-04-26 21:59:14 +02:00
}
2021-01-19 14:18:06 -05:00
isExpressionsSupported ( dsSettings : DataSourceInstanceSettings ) : boolean {
return ( dsSettings . meta . alerting || dsSettings . meta . mixed ) === true ;
}
2021-09-20 09:36:04 -05:00
renderExtraActions() {
2021-11-17 06:05:39 -05:00
return GroupActionComponents . getAllExtraRenderAction ( )
. map ( ( action , index ) = >
action ( {
onAddQuery : this.onAddQuery ,
onChangeDataSource : this.onChangeDataSource ,
key : index ,
} )
)
. filter ( Boolean ) ;
2021-09-20 09:36:04 -05:00
}
2021-01-19 14:18:06 -05:00
renderAddQueryRow ( dsSettings : DataSourceInstanceSettings , styles : QueriesTabStyles ) {
2023-06-16 15:12:52 +02:00
const showAddButton = ! isSharedDashboardQuery ( dsSettings . name ) ;
2020-04-26 21:59:14 +02:00
return (
< HorizontalGroup spacing = "md" align = "flex-start" >
{ showAddButton && (
2020-04-30 09:26:59 +02:00
< Button
icon = "plus"
onClick = { this . onAddQueryClick }
variant = "secondary"
aria - label = { selectors . components . QueryTab . addQuery }
2023-02-22 12:26:41 -03:00
data - testid = "query-tab-add-query"
2020-04-30 09:26:59 +02:00
>
2020-04-26 21:59:14 +02:00
Query
< / Button >
) }
2021-01-22 19:27:33 +02:00
{ config . expressionsEnabled && this . isExpressionsSupported ( dsSettings ) && (
2022-01-20 08:52:36 -05:00
< Button
icon = "plus"
onClick = { this . onAddExpressionClick }
variant = "secondary"
className = { styles . expressionButton }
2023-02-22 12:26:41 -03:00
data - testid = "query-tab-add-expression"
2022-01-20 08:52:36 -05:00
>
< span > Expression & nbsp ; < / span >
< / Button >
2020-04-26 21:59:14 +02:00
) }
2021-09-20 09:36:04 -05:00
{ this . renderExtraActions ( ) }
2020-04-26 21:59:14 +02:00
< / HorizontalGroup >
) ;
}
2019-10-18 13:09:55 +02:00
2022-04-28 20:07:36 +02:00
setScrollRef = ( scrollElement : HTMLDivElement ) : void = > {
this . setState ( { scrollElement } ) ;
} ;
2019-10-18 13:09:55 +02:00
render() {
2022-04-28 20:07:36 +02:00
const { isHelpOpen , dsSettings } = this . state ;
2020-04-26 21:59:14 +02:00
const styles = getStyles ( ) ;
2018-12-12 06:52:08 +01:00
2018-11-09 13:17:41 +01:00
return (
2022-04-28 20:07:36 +02:00
< CustomScrollbar autoHeightMin = "100%" scrollRefCallback = { this . setScrollRef } >
2020-04-26 21:59:14 +02:00
< div className = { styles . innerWrapper } >
{ this . renderTopSection ( styles ) }
2020-12-04 14:24:55 +01:00
{ dsSettings && (
< >
< div className = { styles . queriesWrapper } > { this . renderQueries ( dsSettings ) } < / div >
2021-01-19 14:18:06 -05:00
{ this . renderAddQueryRow ( dsSettings , styles ) }
2020-12-04 14:24:55 +01:00
{ isHelpOpen && (
< Modal title = "Data source help" isOpen = { true } onDismiss = { this . onCloseHelp } >
2023-01-03 13:20:27 +00:00
< PluginHelp pluginId = { dsSettings . meta . id } / >
2020-12-04 14:24:55 +01:00
< / Modal >
) }
< / >
2020-04-26 21:59:14 +02:00
) }
< / div >
< / CustomScrollbar >
2018-11-09 13:17:41 +01:00
) ;
2018-06-26 12:07:41 -07:00
}
}
2020-04-26 21:59:14 +02: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-27 18:29:41 +02:00
margin - right : $ { theme . spacing . inlineFormMargin } ;
2020-04-26 21:59:14 +02:00
` ,
queriesWrapper : css `
padding - bottom : 16px ;
` ,
2021-01-19 14:18:06 -05:00
expressionWrapper : css ` ` ,
expressionButton : css `
margin - right : $ { theme . spacing . sm } ;
` ,
2020-04-26 21:59:14 +02:00
} ;
} ) ;
2021-01-19 14:18:06 -05:00
type QueriesTabStyles = ReturnType < typeof getStyles > ;