2023-04-29 00:19:31 +08:00
import { css , cx } from '@emotion/css' ;
2024-06-25 19:43:47 +08:00
import { useRef } from 'react' ;
import * as React from 'react' ;
2023-04-29 00:19:31 +08:00
import { Observable } from 'rxjs' ;
2023-04-19 21:08:09 +08:00
2023-04-29 00:19:31 +08:00
import { DataSourceInstanceSettings , DataSourceRef , GrafanaTheme2 } from '@grafana/data' ;
2024-02-06 17:20:07 +08:00
import { selectors } from '@grafana/e2e-selectors' ;
2023-04-21 23:07:11 +08:00
import { getTemplateSrv } from '@grafana/runtime' ;
2023-05-04 18:17:03 +08:00
import { useStyles2 , useTheme2 } from '@grafana/ui' ;
2023-10-06 21:11:53 +08:00
import { Trans } from 'app/core/internationalization' ;
2023-04-21 23:07:11 +08:00
2023-04-29 00:19:31 +08:00
import { useDatasources , useKeyboardNavigatableList , useRecentlyUsedDataSources } from '../../hooks' ;
2023-04-19 21:08:09 +08:00
2023-05-04 18:17:03 +08:00
import { AddNewDataSourceButton } from './AddNewDataSourceButton' ;
2023-04-19 21:08:09 +08:00
import { DataSourceCard } from './DataSourceCard' ;
2023-04-21 23:07:11 +08:00
import { getDataSourceCompareFn , isDataSourceMatch } from './utils' ;
2023-04-19 21:08:09 +08:00
/ * *
* Component props description for the { @link DataSourceList }
*
* @internal
* /
export interface DataSourceListProps {
className? : string ;
onChange : ( ds : DataSourceInstanceSettings ) = > void ;
2023-04-20 18:11:13 +08:00
current : DataSourceRef | DataSourceInstanceSettings | string | null | undefined ;
/** Would be nicer if these parameters were part of a filtering object */
2023-04-19 21:08:09 +08:00
tracing? : boolean ;
mixed? : boolean ;
dashboard? : boolean ;
metrics? : boolean ;
type ? : string | string [ ] ;
annotations? : boolean ;
variables? : boolean ;
alerting? : boolean ;
pluginId? : string ;
/** If true,we show only DSs with logs; and if true, pluginId shouldnt be passed in */
logs? : boolean ;
width? : number ;
2023-04-29 00:19:31 +08:00
keyboardEvents? : Observable < React.KeyboardEvent > ;
2023-04-19 21:08:09 +08:00
inputId? : string ;
filter ? : ( dataSource : DataSourceInstanceSettings ) = > boolean ;
onClear ? : ( ) = > void ;
2023-05-04 18:17:03 +08:00
onClickEmptyStateCTA ? : ( ) = > void ;
2023-04-29 00:19:31 +08:00
enableKeyboardNavigation? : boolean ;
2023-04-19 21:08:09 +08:00
}
2023-04-20 18:11:13 +08:00
export function DataSourceList ( props : DataSourceListProps ) {
2023-04-29 00:19:31 +08:00
const containerRef = useRef < HTMLDivElement > ( null ) ;
const [ navigatableProps , selectedItemCssSelector ] = useKeyboardNavigatableList ( {
keyboardEvents : props.keyboardEvents ,
containerRef : containerRef ,
} ) ;
const theme = useTheme2 ( ) ;
const styles = getStyles ( theme , selectedItemCssSelector ) ;
2023-05-04 18:17:03 +08:00
const { className , current , onChange , enableKeyboardNavigation , onClickEmptyStateCTA } = props ;
2023-04-21 23:07:11 +08:00
const dataSources = useDatasources ( {
2023-04-20 18:11:13 +08:00
alerting : props.alerting ,
annotations : props.annotations ,
dashboard : props.dashboard ,
logs : props.logs ,
metrics : props.metrics ,
mixed : props.mixed ,
pluginId : props.pluginId ,
tracing : props.tracing ,
type : props . type ,
variables : props.variables ,
} ) ;
2023-04-21 23:07:11 +08:00
const [ recentlyUsedDataSources , pushRecentlyUsedDataSource ] = useRecentlyUsedDataSources ( ) ;
2023-05-04 18:17:03 +08:00
const filteredDataSources = props . filter ? dataSources . filter ( props . filter ) : dataSources ;
2023-04-21 23:07:11 +08:00
2023-04-20 18:11:13 +08:00
return (
2024-02-06 17:20:07 +08:00
< div
ref = { containerRef }
className = { cx ( className , styles . container ) }
data - testid = { selectors . components . DataSourcePicker . dataSourceList }
>
2023-05-04 18:17:03 +08:00
{ filteredDataSources . length === 0 && (
< EmptyState className = { styles . emptyState } onClickCTA = { onClickEmptyStateCTA } / >
) }
{ filteredDataSources
2023-04-21 23:07:11 +08:00
. sort ( getDataSourceCompareFn ( current , recentlyUsedDataSources , getDataSourceVariableIDs ( ) ) )
2023-04-20 18:11:13 +08:00
. map ( ( ds ) = > (
2023-04-19 21:08:09 +08:00
< DataSourceCard
2023-06-21 16:55:55 +08:00
data - testid = "data-source-card"
2023-04-19 21:08:09 +08:00
key = { ds . uid }
ds = { ds }
2023-04-21 23:07:11 +08:00
onClick = { ( ) = > {
pushRecentlyUsedDataSource ( ds ) ;
onChange ( ds ) ;
} }
2023-06-14 16:52:49 +08:00
selected = { isDataSourceMatch ( ds , current ) }
2023-04-29 00:19:31 +08:00
{ . . . ( enableKeyboardNavigation ? navigatableProps : { } ) }
2023-04-19 21:08:09 +08:00
/ >
) ) }
2023-04-20 18:11:13 +08:00
< / div >
) ;
2023-04-19 21:08:09 +08:00
}
2023-04-21 23:07:11 +08:00
2023-05-04 18:17:03 +08:00
function EmptyState ( { className , onClickCTA } : { className? : string ; onClickCTA ? : ( ) = > void } ) {
const styles = useStyles2 ( getEmptyStateStyles ) ;
return (
< div className = { cx ( className , styles . container ) } >
2023-10-06 21:11:53 +08:00
< p className = { styles . message } >
< Trans i18nKey = "data-source-picker.list.no-data-source-message" > No data sources found < / Trans >
< / p >
2023-05-04 18:17:03 +08:00
< AddNewDataSourceButton onClick = { onClickCTA } / >
< / div >
) ;
}
function getEmptyStateStyles ( theme : GrafanaTheme2 ) {
return {
2024-10-23 22:25:28 +08:00
container : css ( {
display : 'flex' ,
flexDirection : 'column' ,
justifyContent : 'center' ,
alignItems : 'center' ,
} ) ,
message : css ( {
marginBottom : theme.spacing ( 3 ) ,
} ) ,
2023-05-04 18:17:03 +08:00
} ;
}
2023-04-21 23:07:11 +08:00
function getDataSourceVariableIDs() {
const templateSrv = getTemplateSrv ( ) ;
/** Unforunately there is no easy way to identify data sources that are variables. The uid of the data source will be the name of the variable in a templating syntax $([name]) **/
return templateSrv
. getVariables ( )
. filter ( ( v ) = > v . type === 'datasource' )
. map ( ( v ) = > ` \ ${ $ { v . id } } ` ) ;
}
2023-04-29 00:19:31 +08:00
function getStyles ( theme : GrafanaTheme2 , selectedItemCssSelector : string ) {
return {
2024-10-23 22:25:28 +08:00
container : css ( {
display : 'flex' ,
flexDirection : 'column' ,
[ ` ${ selectedItemCssSelector } ` ] : {
backgroundColor : theme.colors.background.secondary ,
} ,
} ) ,
emptyState : css ( {
height : '100%' ,
flex : 1 ,
} ) ,
2023-04-29 00:19:31 +08:00
} ;
}