mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DataSourcePicker: Add recently used from local storage to ds picker (#66985)
* Add recently used from local storage to ds picker
This commit is contained in:
parent
1cad819670
commit
d419402a43
@ -10,11 +10,13 @@ import { DataSourceJsonData } from '@grafana/schema';
|
||||
import { Button, CustomScrollbar, Icon, Input, ModalsController, Portal, useStyles2 } from '@grafana/ui';
|
||||
import config from 'app/core/config';
|
||||
|
||||
import { useDatasource } from '../../hooks';
|
||||
|
||||
import { DataSourceList } from './DataSourceList';
|
||||
import { DataSourceLogo, DataSourceLogoPlaceHolder } from './DataSourceLogo';
|
||||
import { DataSourceModal } from './DataSourceModal';
|
||||
import { PickerContentProps, DataSourceDropdownProps } from './types';
|
||||
import { dataSourceLabel, useGetDatasource } from './utils';
|
||||
import { dataSourceLabel } from './utils';
|
||||
|
||||
export function DataSourceDropdown(props: DataSourceDropdownProps) {
|
||||
const { current, onChange, ...restProps } = props;
|
||||
@ -24,7 +26,7 @@ export function DataSourceDropdown(props: DataSourceDropdownProps) {
|
||||
const [selectorElement, setSelectorElement] = useState<HTMLDivElement | null>();
|
||||
const [filterTerm, setFilterTerm] = useState<string>();
|
||||
|
||||
const currentDataSourceInstanceSettings = useGetDatasource(current);
|
||||
const currentDataSourceInstanceSettings = useDatasource(current);
|
||||
|
||||
const popper = usePopper(markerElement, selectorElement, {
|
||||
placement: 'bottom-start',
|
||||
|
@ -1,9 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
import { DataSourceInstanceSettings, DataSourceRef } from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import { useDatasources, useRecentlyUsedDataSources } from '../../hooks';
|
||||
|
||||
import { DataSourceCard } from './DataSourceCard';
|
||||
import { isDataSourceMatch, useGetDatasources } from './utils';
|
||||
import { getDataSourceCompareFn, isDataSourceMatch } from './utils';
|
||||
|
||||
/**
|
||||
* Component props description for the {@link DataSourceList}
|
||||
@ -35,7 +38,7 @@ export interface DataSourceListProps {
|
||||
export function DataSourceList(props: DataSourceListProps) {
|
||||
const { className, current, onChange } = props;
|
||||
// QUESTION: Should we use data from the Redux store as admin DS view does?
|
||||
const dataSources = useGetDatasources({
|
||||
const dataSources = useDatasources({
|
||||
alerting: props.alerting,
|
||||
annotations: props.annotations,
|
||||
dashboard: props.dashboard,
|
||||
@ -48,18 +51,33 @@ export function DataSourceList(props: DataSourceListProps) {
|
||||
variables: props.variables,
|
||||
});
|
||||
|
||||
const [recentlyUsedDataSources, pushRecentlyUsedDataSource] = useRecentlyUsedDataSources();
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{dataSources
|
||||
.filter((ds) => (props.filter ? props.filter(ds) : true))
|
||||
.sort(getDataSourceCompareFn(current, recentlyUsedDataSources, getDataSourceVariableIDs()))
|
||||
.map((ds) => (
|
||||
<DataSourceCard
|
||||
key={ds.uid}
|
||||
ds={ds}
|
||||
onClick={() => onChange(ds)}
|
||||
onClick={() => {
|
||||
pushRecentlyUsedDataSource(ds);
|
||||
onChange(ds);
|
||||
}}
|
||||
selected={!!isDataSourceMatch(ds, current)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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}}`);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DataSourceInstanceSettings, DataSourceRef } from '@grafana/data';
|
||||
|
||||
import { isDataSourceMatch } from './utils';
|
||||
import { isDataSourceMatch, getDataSourceCompareFn } from './utils';
|
||||
|
||||
describe('isDataSourceMatch', () => {
|
||||
const dataSourceInstanceSettings = { uid: 'a' } as DataSourceInstanceSettings;
|
||||
@ -28,3 +28,68 @@ describe('isDataSourceMatch', () => {
|
||||
expect(isDataSourceMatch(dataSourceInstanceSettings, { uid: 'b' } as DataSourceRef)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDataSouceCompareFn', () => {
|
||||
const dataSources = [
|
||||
{ uid: 'c', name: 'c', meta: { builtIn: false } },
|
||||
{ uid: 'a', name: 'a', meta: { builtIn: true } },
|
||||
{ uid: 'b', name: 'b', meta: { builtIn: false } },
|
||||
] as DataSourceInstanceSettings[];
|
||||
|
||||
it('sorts built in datasources last and other data sources alphabetically', () => {
|
||||
dataSources.sort(getDataSourceCompareFn(undefined, [], []));
|
||||
expect(dataSources).toEqual([
|
||||
{ uid: 'b', name: 'b', meta: { builtIn: false } },
|
||||
{ uid: 'c', name: 'c', meta: { builtIn: false } },
|
||||
{ uid: 'a', name: 'a', meta: { builtIn: true } },
|
||||
] as DataSourceInstanceSettings[]);
|
||||
});
|
||||
|
||||
it('sorts the current datasource before others', () => {
|
||||
dataSources.sort(getDataSourceCompareFn('c', [], []));
|
||||
expect(dataSources).toEqual([
|
||||
{ uid: 'c', name: 'c', meta: { builtIn: false } },
|
||||
{ uid: 'b', name: 'b', meta: { builtIn: false } },
|
||||
{ uid: 'a', name: 'a', meta: { builtIn: true } },
|
||||
] as DataSourceInstanceSettings[]);
|
||||
});
|
||||
|
||||
it('sorts recently used datasources first', () => {
|
||||
dataSources.sort(getDataSourceCompareFn(undefined, ['c', 'a'], []));
|
||||
expect(dataSources).toEqual([
|
||||
{ uid: 'a', name: 'a', meta: { builtIn: true } },
|
||||
{ uid: 'c', name: 'c', meta: { builtIn: false } },
|
||||
{ uid: 'b', name: 'b', meta: { builtIn: false } },
|
||||
] as DataSourceInstanceSettings[]);
|
||||
});
|
||||
|
||||
it('sorts variables before other datasources', () => {
|
||||
dataSources.sort(getDataSourceCompareFn(undefined, [], ['c', 'b']));
|
||||
expect(dataSources).toEqual([
|
||||
{ uid: 'b', name: 'b', meta: { builtIn: false } },
|
||||
{ uid: 'c', name: 'c', meta: { builtIn: false } },
|
||||
{ uid: 'a', name: 'a', meta: { builtIn: true } },
|
||||
] as DataSourceInstanceSettings[]);
|
||||
});
|
||||
|
||||
it('sorts datasources current -> recently used -> variables -> others -> built in', () => {
|
||||
const dataSources = [
|
||||
{ uid: 'a', name: 'a', meta: { builtIn: true } },
|
||||
{ uid: 'b', name: 'b', meta: { builtIn: false } },
|
||||
{ uid: 'c', name: 'c', meta: { builtIn: false } },
|
||||
{ uid: 'e', name: 'e', meta: { builtIn: false } },
|
||||
{ uid: 'd', name: 'd', meta: { builtIn: false } },
|
||||
{ uid: 'f', name: 'f', meta: { builtIn: false } },
|
||||
] as DataSourceInstanceSettings[];
|
||||
|
||||
dataSources.sort(getDataSourceCompareFn('c', ['b', 'e'], ['d']));
|
||||
expect(dataSources).toEqual([
|
||||
{ uid: 'c', name: 'c', meta: { builtIn: false } },
|
||||
{ uid: 'e', name: 'e', meta: { builtIn: false } },
|
||||
{ uid: 'b', name: 'b', meta: { builtIn: false } },
|
||||
{ uid: 'd', name: 'd', meta: { builtIn: false } },
|
||||
{ uid: 'f', name: 'f', meta: { builtIn: false } },
|
||||
{ uid: 'a', name: 'a', meta: { builtIn: true } },
|
||||
] as DataSourceInstanceSettings[]);
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { DataSourceInstanceSettings, DataSourceJsonData, DataSourceRef } from '@grafana/data';
|
||||
import { GetDataSourceListFilters, getDataSourceSrv } from '@grafana/runtime';
|
||||
|
||||
export function isDataSourceMatch(
|
||||
ds: DataSourceInstanceSettings | undefined,
|
||||
@ -39,22 +38,52 @@ export function dataSourceLabel(
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
export function useGetDatasources(filters: GetDataSourceListFilters) {
|
||||
const dataSourceSrv = getDataSourceSrv();
|
||||
export function getDataSourceCompareFn(
|
||||
current: DataSourceRef | DataSourceInstanceSettings | string | null | undefined,
|
||||
recentlyUsedDataSources: string[],
|
||||
dataSourceVariablesIDs: string[]
|
||||
) {
|
||||
const cmpDataSources = (a: DataSourceInstanceSettings, b: DataSourceInstanceSettings) => {
|
||||
// Sort the current ds before everything else.
|
||||
if (current && isDataSourceMatch(a, current)) {
|
||||
return -1;
|
||||
} else if (current && isDataSourceMatch(b, current)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return dataSourceSrv.getList(filters);
|
||||
}
|
||||
|
||||
export function useGetDatasource(dataSource: string | DataSourceRef | DataSourceInstanceSettings | null | undefined) {
|
||||
const dataSourceSrv = getDataSourceSrv();
|
||||
|
||||
if (!dataSource) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof dataSource === 'string') {
|
||||
return dataSourceSrv.getInstanceSettings(dataSource);
|
||||
}
|
||||
|
||||
return dataSourceSrv.getInstanceSettings(dataSource);
|
||||
// Sort recently used data sources by latest used, but after current.
|
||||
const aIndex = recentlyUsedDataSources.indexOf(a.uid);
|
||||
const bIndex = recentlyUsedDataSources.indexOf(b.uid);
|
||||
if (aIndex > -1 && aIndex > bIndex) {
|
||||
return -1;
|
||||
}
|
||||
if (bIndex > -1 && bIndex > aIndex) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Sort variables before the rest. Variables sorted alphabetically by name.
|
||||
const aIsVariable = dataSourceVariablesIDs.includes(a.uid);
|
||||
const bIsVariable = dataSourceVariablesIDs.includes(b.uid);
|
||||
if (aIsVariable && !bIsVariable) {
|
||||
return -1;
|
||||
} else if (bIsVariable && !aIsVariable) {
|
||||
return 1;
|
||||
} else if (bIsVariable && aIsVariable) {
|
||||
return a.name < b.name ? -1 : 1;
|
||||
}
|
||||
|
||||
// Sort built in DataSources to the bottom and alphabetically by name.
|
||||
if (a.meta.builtIn && !b.meta.builtIn) {
|
||||
return 1;
|
||||
} else if (b.meta.builtIn && !a.meta.builtIn) {
|
||||
return -1;
|
||||
} else if (a.meta.builtIn && b.meta.builtIn) {
|
||||
return a.name < b.name ? -1 : 1;
|
||||
}
|
||||
|
||||
// Sort the rest alphabetically by name.
|
||||
return a.name < b.name ? -1 : 1;
|
||||
};
|
||||
|
||||
return cmpDataSources;
|
||||
}
|
||||
|
57
public/app/features/datasources/hooks.ts
Normal file
57
public/app/features/datasources/hooks.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
|
||||
import { DataSourceInstanceSettings, DataSourceRef } from '@grafana/data';
|
||||
import { GetDataSourceListFilters, getDataSourceSrv } from '@grafana/runtime';
|
||||
|
||||
export const LOCAL_STORAGE_KEY = 'grafana.features.datasources.components.picker.DataSourceDropDown.history';
|
||||
|
||||
/**
|
||||
* Stores the uid of the last 5 data sources selected by the user. The last UID is the one most recently used.
|
||||
*/
|
||||
export function useRecentlyUsedDataSources(): [string[], (ds: DataSourceInstanceSettings) => void] {
|
||||
const [value = [], setStorage] = useLocalStorage<string[]>(LOCAL_STORAGE_KEY, []);
|
||||
|
||||
const pushRecentlyUsedDataSource = useCallback(
|
||||
(ds: DataSourceInstanceSettings) => {
|
||||
if (ds.meta.builtIn) {
|
||||
// Prevent storing the built in datasources (-- Grafana --, -- Mixed --, -- Dashboard --)
|
||||
return;
|
||||
}
|
||||
if (value.includes(ds.uid)) {
|
||||
// Prevent storing multiple copies of the same data source, put it at the front of the array instead.
|
||||
value.splice(
|
||||
value.findIndex((dsUid) => ds.uid === dsUid),
|
||||
1
|
||||
);
|
||||
setStorage([...value, ds.uid]);
|
||||
} else {
|
||||
setStorage([...value, ds.uid].slice(1, 6));
|
||||
}
|
||||
},
|
||||
[value, setStorage]
|
||||
);
|
||||
|
||||
return [value, pushRecentlyUsedDataSource];
|
||||
}
|
||||
|
||||
export function useDatasources(filters: GetDataSourceListFilters) {
|
||||
const dataSourceSrv = getDataSourceSrv();
|
||||
const dataSources = dataSourceSrv.getList(filters);
|
||||
|
||||
return dataSources;
|
||||
}
|
||||
|
||||
export function useDatasource(dataSource: string | DataSourceRef | DataSourceInstanceSettings | null | undefined) {
|
||||
const dataSourceSrv = getDataSourceSrv();
|
||||
|
||||
if (!dataSource) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof dataSource === 'string') {
|
||||
return dataSourceSrv.getInstanceSettings(dataSource);
|
||||
}
|
||||
|
||||
return dataSourceSrv.getInstanceSettings(dataSource);
|
||||
}
|
Loading…
Reference in New Issue
Block a user