2023-04-29 00:19:31 +08:00
import { css , cx } from '@emotion/css' ;
import React , { useRef } from 'react' ;
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' ;
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-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-20 18:11:13 +08:00
// QUESTION: Should we use data from the Redux store as admin DS view does?
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 (
2023-04-29 00:19:31 +08:00
< div ref = { containerRef } className = { cx ( className , styles . container ) } >
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
key = { ds . uid }
ds = { ds }
2023-04-21 23:07:11 +08:00
onClick = { ( ) = > {
pushRecentlyUsedDataSource ( ds ) ;
onChange ( ds ) ;
} }
2023-04-19 21:08:09 +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 ) } >
< p className = { styles . message } > No data sources found < / p >
< AddNewDataSourceButton onClick = { onClickCTA } / >
< / div >
) ;
}
function getEmptyStateStyles ( theme : GrafanaTheme2 ) {
return {
container : css `
display : flex ;
flex - direction : column ;
justify - content : center ;
align - items : center ;
` ,
message : css `
margin - bottom : $ { theme . spacing ( 3 ) } ;
` ,
} ;
}
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 {
container : css `
2023-05-04 18:17:03 +08:00
display : flex ;
flex - direction : column ;
2023-04-29 00:19:31 +08:00
$ { selectedItemCssSelector } {
background - color : $ { theme . colors . background . secondary } ;
}
` ,
2023-05-04 18:17:03 +08:00
emptyState : css `
height : 100 % ;
flex : 1 ;
` ,
2023-04-29 00:19:31 +08:00
} ;
}