mirror of https://github.com/grafana/grafana
parent
59d87fe3f1
commit
2c2b8e2f1e
@ -0,0 +1,181 @@ |
||||
import { useState } from 'react'; |
||||
import { PopValueActionMeta, RemoveValueActionMeta } from 'react-select'; |
||||
|
||||
import { |
||||
DataSourceInstanceSettings, |
||||
SelectableValue, |
||||
getDataSourceUID, |
||||
isUnsignedPluginSignature, |
||||
} from '@grafana/data'; |
||||
import { selectors } from '@grafana/e2e-selectors'; |
||||
import { DataSourcePickerProps, DataSourcePickerState, getDataSourceSrv } from '@grafana/runtime'; |
||||
import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; |
||||
import { ActionMeta, MultiSelect, PluginSignatureBadge, Stack } from '@grafana/ui'; |
||||
|
||||
import { isDataSourceManagingAlerts } from '../../utils/datasource'; |
||||
|
||||
export interface MultipleDataSourcePickerProps extends Omit<DataSourcePickerProps, 'onChange' | 'current'> { |
||||
onChange: (ds: DataSourceInstanceSettings, action: 'add' | 'remove') => void; |
||||
current: string[] | undefined; |
||||
} |
||||
|
||||
export const MultipleDataSourcePicker = (props: MultipleDataSourcePickerProps) => { |
||||
const dataSourceSrv = getDataSourceSrv(); |
||||
|
||||
const [state, setState] = useState<DataSourcePickerState>(); |
||||
|
||||
const onChange = (items: Array<SelectableValue<string>>, actionMeta: ActionMeta) => { |
||||
if (actionMeta.action === 'clear' && props.onClear) { |
||||
props.onClear(); |
||||
return; |
||||
} |
||||
|
||||
const selectedItem = items[items.length - 1]; |
||||
|
||||
let dataSourceName, action: 'add' | 'remove'; |
||||
|
||||
if (actionMeta.action === 'pop-value' || actionMeta.action === 'remove-value') { |
||||
const castedActionMeta: |
||||
| RemoveValueActionMeta<SelectableValue<string>> |
||||
| PopValueActionMeta<SelectableValue<string>> = actionMeta; |
||||
dataSourceName = castedActionMeta.removedValue?.value; |
||||
action = 'remove'; |
||||
} else { |
||||
dataSourceName = selectedItem.value; |
||||
action = 'add'; |
||||
} |
||||
|
||||
const dsSettings = dataSourceSrv.getInstanceSettings(dataSourceName); |
||||
|
||||
if (dsSettings) { |
||||
props.onChange(dsSettings, action); |
||||
setState({ error: undefined }); |
||||
} |
||||
}; |
||||
|
||||
const getCurrentValue = (): Array<SelectableValue<string>> | undefined => { |
||||
const { current, hideTextValue, noDefault } = props; |
||||
if (!current && noDefault) { |
||||
return; |
||||
} |
||||
|
||||
return current?.map((dataSourceName: string) => { |
||||
const ds = dataSourceSrv.getInstanceSettings(dataSourceName); |
||||
if (ds) { |
||||
return { |
||||
label: ds.name.slice(0, 37), |
||||
value: ds.name, |
||||
imgUrl: ds.meta.info.logos.small, |
||||
hideText: hideTextValue, |
||||
meta: ds.meta, |
||||
}; |
||||
} |
||||
|
||||
const uid = getDataSourceUID(dataSourceName); |
||||
|
||||
if (uid === ExpressionDatasourceRef.uid || uid === ExpressionDatasourceRef.name) { |
||||
return { label: uid, value: uid, hideText: hideTextValue }; |
||||
} |
||||
|
||||
return { |
||||
label: (uid ?? 'no name') + ' - not found', |
||||
value: uid ?? undefined, |
||||
imgUrl: '', |
||||
hideText: hideTextValue, |
||||
}; |
||||
}); |
||||
}; |
||||
|
||||
const getDataSourceOptions = () => { |
||||
const { alerting, tracing, metrics, mixed, dashboard, variables, annotations, pluginId, type, filter, logs } = |
||||
props; |
||||
|
||||
const dataSources = dataSourceSrv.getList({ |
||||
alerting, |
||||
tracing, |
||||
metrics, |
||||
logs, |
||||
dashboard, |
||||
mixed, |
||||
variables, |
||||
annotations, |
||||
pluginId, |
||||
filter, |
||||
type, |
||||
}); |
||||
|
||||
const alertManagingDs = dataSources.filter(isDataSourceManagingAlerts).map((ds) => ({ |
||||
value: ds.name, |
||||
label: `${ds.name}${ds.isDefault ? ' (default)' : ''}`, |
||||
imgUrl: ds.meta.info.logos.small, |
||||
meta: ds.meta, |
||||
})); |
||||
|
||||
const nonAlertManagingDs = dataSources |
||||
.filter((ds) => !isDataSourceManagingAlerts(ds)) |
||||
.map((ds) => ({ |
||||
value: ds.name, |
||||
label: `${ds.name}${ds.isDefault ? ' (default)' : ''}`, |
||||
imgUrl: ds.meta.info.logos.small, |
||||
meta: ds.meta, |
||||
})); |
||||
|
||||
const groupedOptions = [ |
||||
{ label: 'Data sources with configured alert rules', options: alertManagingDs, expanded: true }, |
||||
{ label: 'Other data sources', options: nonAlertManagingDs, expanded: true }, |
||||
]; |
||||
|
||||
return groupedOptions; |
||||
}; |
||||
|
||||
const { |
||||
autoFocus, |
||||
onBlur, |
||||
onClear, |
||||
openMenuOnFocus, |
||||
placeholder, |
||||
width, |
||||
inputId, |
||||
disabled = false, |
||||
isLoading = false, |
||||
} = props; |
||||
|
||||
const options = getDataSourceOptions(); |
||||
const value = getCurrentValue(); |
||||
const isClearable = typeof onClear === 'function'; |
||||
|
||||
return ( |
||||
<div data-testid={selectors.components.DataSourcePicker.container}> |
||||
<MultiSelect |
||||
isLoading={isLoading} |
||||
disabled={disabled} |
||||
data-testid={selectors.components.DataSourcePicker.inputV2} |
||||
inputId={inputId || 'data-source-picker'} |
||||
className="ds-picker select-container" |
||||
isClearable={isClearable} |
||||
backspaceRemovesValue={true} |
||||
onChange={onChange} |
||||
options={options} |
||||
autoFocus={autoFocus} |
||||
onBlur={onBlur} |
||||
width={width} |
||||
openMenuOnFocus={openMenuOnFocus} |
||||
maxMenuHeight={500} |
||||
placeholder={placeholder} |
||||
noOptionsMessage="No datasources found" |
||||
value={value ?? []} |
||||
invalid={Boolean(state?.error) || Boolean(props.invalid)} |
||||
getOptionLabel={(o) => { |
||||
if (o.meta && isUnsignedPluginSignature(o.meta.signature) && o !== value) { |
||||
return ( |
||||
<Stack alignItems="center" justifyContent="space-between"> |
||||
<span>{o.label}</span> <PluginSignatureBadge status={o.meta.signature} /> |
||||
</Stack> |
||||
); |
||||
} |
||||
return o.label || ''; |
||||
}} |
||||
/> |
||||
</div> |
||||
); |
||||
}; |
Loading…
Reference in new issue