mirror of
https://github.com/grafana/grafana.git
synced 2025-09-19 07:33:48 +08:00
Filter data sources by favorite setting (#109593)
This commit is contained in:

committed by
GitHub

parent
9453cedb51
commit
d08ea58243
@ -57,7 +57,7 @@ export { hasPermission, hasPermissionInMetadata, hasAllPermissions, hasAnyPermis
|
|||||||
export { QueryEditorWithMigration } from './components/QueryEditorWithMigration';
|
export { QueryEditorWithMigration } from './components/QueryEditorWithMigration';
|
||||||
export { type MigrationHandler, isMigrationHandler, migrateQuery, migrateRequest } from './utils/migrationHandler';
|
export { type MigrationHandler, isMigrationHandler, migrateQuery, migrateRequest } from './utils/migrationHandler';
|
||||||
export { usePluginUserStorage } from './utils/userStorage';
|
export { usePluginUserStorage } from './utils/userStorage';
|
||||||
export { useFavoriteDatasources } from './utils/useFavoriteDatasources';
|
export { useFavoriteDatasources, type FavoriteDatasources } from './utils/useFavoriteDatasources';
|
||||||
export { FolderPicker, setFolderPicker } from './components/FolderPicker';
|
export { FolderPicker, setFolderPicker } from './components/FolderPicker';
|
||||||
export {
|
export {
|
||||||
type CorrelationsService,
|
type CorrelationsService,
|
||||||
|
@ -17,6 +17,16 @@ jest.mock('./userStorage', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('../config', () => {
|
||||||
|
return {
|
||||||
|
config: {
|
||||||
|
featureToggles: {
|
||||||
|
favoriteDatasources: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('useFavoriteDatasources', () => {
|
describe('useFavoriteDatasources', () => {
|
||||||
// Test data helpers
|
// Test data helpers
|
||||||
const pluginMetaInfo: PluginMetaInfo = {
|
const pluginMetaInfo: PluginMetaInfo = {
|
||||||
|
@ -2,16 +2,28 @@ import { useCallback, useEffect, useState } from 'react';
|
|||||||
|
|
||||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||||
|
|
||||||
|
import { config } from '../config';
|
||||||
|
|
||||||
import { UserStorage } from './userStorage';
|
import { UserStorage } from './userStorage';
|
||||||
|
|
||||||
const FAVORITE_DATASOURCES_KEY = 'favoriteDatasources';
|
const FAVORITE_DATASOURCES_KEY = 'favoriteDatasources';
|
||||||
|
|
||||||
|
export type FavoriteDatasources = {
|
||||||
|
enabled: boolean;
|
||||||
|
favoriteDatasources: string[];
|
||||||
|
initialFavoriteDataSources: string[];
|
||||||
|
addFavoriteDatasource: (ds: DataSourceInstanceSettings) => void;
|
||||||
|
removeFavoriteDatasource: (ds: DataSourceInstanceSettings) => void;
|
||||||
|
isFavoriteDatasource: (dsUid: string) => boolean;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A hook for managing favorite data sources using user storage.
|
* A hook for managing favorite data sources using user storage.
|
||||||
* This hook provides functionality to store and retrieve a list of favorite data source UIDs
|
* This hook provides functionality to store and retrieve a list of favorite data source UIDs
|
||||||
* using the backend user storage (with localStorage fallback).
|
* using the backend user storage (with localStorage fallback).
|
||||||
*
|
*
|
||||||
* @returns An object containing:
|
* @returns An object containing:
|
||||||
|
* - A boolean indicating if the feature is enabled
|
||||||
* - An array of favorite data source UIDs
|
* - An array of favorite data source UIDs
|
||||||
* - An array of favorite data source UIDs that were initially loaded from storage
|
* - An array of favorite data source UIDs that were initially loaded from storage
|
||||||
* - A function to add a data source to favorites
|
* - A function to add a data source to favorites
|
||||||
@ -19,13 +31,18 @@ const FAVORITE_DATASOURCES_KEY = 'favoriteDatasources';
|
|||||||
* - A function to check if a data source is favorited
|
* - A function to check if a data source is favorited
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function useFavoriteDatasources(): {
|
export function useFavoriteDatasources(): FavoriteDatasources {
|
||||||
favoriteDatasources: string[];
|
if (!config.featureToggles.favoriteDatasources) {
|
||||||
initialFavoriteDataSources: string[];
|
return {
|
||||||
addFavoriteDatasource: (ds: DataSourceInstanceSettings) => void;
|
enabled: false,
|
||||||
removeFavoriteDatasource: (ds: DataSourceInstanceSettings) => void;
|
favoriteDatasources: [],
|
||||||
isFavoriteDatasource: (dsUid: string) => boolean;
|
initialFavoriteDataSources: [],
|
||||||
} {
|
addFavoriteDatasource: () => {},
|
||||||
|
removeFavoriteDatasource: () => {},
|
||||||
|
isFavoriteDatasource: () => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const [userStorage] = useState(() => new UserStorage('grafana-runtime'));
|
const [userStorage] = useState(() => new UserStorage('grafana-runtime'));
|
||||||
const [favoriteDatasources, setFavoriteDatasources] = useState<string[]>([]);
|
const [favoriteDatasources, setFavoriteDatasources] = useState<string[]>([]);
|
||||||
const [initialFavoriteDataSources, setInitialFavoriteDataSources] = useState<string[]>([]);
|
const [initialFavoriteDataSources, setInitialFavoriteDataSources] = useState<string[]>([]);
|
||||||
@ -86,6 +103,7 @@ export function useFavoriteDatasources(): {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
enabled: true,
|
||||||
favoriteDatasources,
|
favoriteDatasources,
|
||||||
addFavoriteDatasource,
|
addFavoriteDatasource,
|
||||||
removeFavoriteDatasource,
|
removeFavoriteDatasource,
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
import { PureComponent } from 'react';
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue, GrafanaTheme2 } from '@grafana/data';
|
||||||
import { LinkButton, FilterInput, InlineField } from '@grafana/ui';
|
import { LinkButton, FilterInput, InlineField, Checkbox, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { SortPicker } from '../Select/SortPicker';
|
import { SortPicker } from '../Select/SortPicker';
|
||||||
|
|
||||||
|
export type FilterCheckbox = {
|
||||||
|
onChange: (value: boolean) => void;
|
||||||
|
value: boolean;
|
||||||
|
label?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
setSearchQuery: (value: string) => void;
|
setSearchQuery: (value: string) => void;
|
||||||
@ -16,41 +22,59 @@ export interface Props {
|
|||||||
value?: string;
|
value?: string;
|
||||||
getSortOptions?: () => Promise<SelectableValue[]>;
|
getSortOptions?: () => Promise<SelectableValue[]>;
|
||||||
};
|
};
|
||||||
|
filterCheckbox?: FilterCheckbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class PageActionBar extends PureComponent<Props> {
|
export default function PageActionBar({
|
||||||
render() {
|
searchQuery,
|
||||||
const {
|
linkButton,
|
||||||
searchQuery,
|
setSearchQuery,
|
||||||
linkButton,
|
target,
|
||||||
setSearchQuery,
|
placeholder = 'Search by name or type',
|
||||||
target,
|
sortPicker,
|
||||||
placeholder = 'Search by name or type',
|
filterCheckbox,
|
||||||
sortPicker,
|
}: Props) {
|
||||||
} = this.props;
|
const styles = useStyles2(getStyles);
|
||||||
const linkProps: Omit<Parameters<typeof LinkButton>[0], 'children'> = {
|
const linkProps: Omit<Parameters<typeof LinkButton>[0], 'children'> = {
|
||||||
href: linkButton?.href,
|
href: linkButton?.href,
|
||||||
disabled: linkButton?.disabled,
|
disabled: linkButton?.disabled,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (target) {
|
if (target) {
|
||||||
linkProps.target = target;
|
linkProps.target = target;
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="page-action-bar">
|
|
||||||
<InlineField grow>
|
|
||||||
<FilterInput value={searchQuery} onChange={setSearchQuery} placeholder={placeholder} />
|
|
||||||
</InlineField>
|
|
||||||
{sortPicker && (
|
|
||||||
<SortPicker
|
|
||||||
onChange={sortPicker.onChange}
|
|
||||||
value={sortPicker.value}
|
|
||||||
getSortOptions={sortPicker.getSortOptions}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{linkButton && <LinkButton {...linkProps}>{linkButton.title}</LinkButton>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<InlineField grow>
|
||||||
|
<FilterInput value={searchQuery} onChange={setSearchQuery} placeholder={placeholder} />
|
||||||
|
</InlineField>
|
||||||
|
{filterCheckbox && (
|
||||||
|
<Checkbox
|
||||||
|
label={filterCheckbox.label}
|
||||||
|
value={filterCheckbox.value}
|
||||||
|
onChange={(event) => filterCheckbox.onChange(event.currentTarget.checked)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{sortPicker && (
|
||||||
|
<SortPicker
|
||||||
|
onChange={sortPicker.onChange}
|
||||||
|
value={sortPicker.value}
|
||||||
|
getSortOptions={sortPicker.getSortOptions}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{linkButton && <LinkButton {...linkProps}>{linkButton.title}</LinkButton>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
container: css({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -1,24 +1,69 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import { render } from 'test/test-utils';
|
import { render } from 'test/test-utils';
|
||||||
|
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
import { getMockDataSources } from '../mocks/dataSourcesMocks';
|
import { getMockDataSources } from '../mocks/dataSourcesMocks';
|
||||||
|
|
||||||
import { DataSourcesListView } from './DataSourcesList';
|
import { DataSourcesListView, ViewProps } from './DataSourcesList';
|
||||||
|
|
||||||
const setup = () => {
|
// Mock the useFavoriteDatasources hook
|
||||||
return render(
|
const mockIsFavoriteDatasource = jest.fn();
|
||||||
<DataSourcesListView
|
const mockUseFavoriteDatasources = jest.fn(() => ({
|
||||||
dataSources={getMockDataSources(3)}
|
enabled: true,
|
||||||
dataSourcesCount={3}
|
isFavoriteDatasource: mockIsFavoriteDatasource,
|
||||||
isLoading={false}
|
favoriteDatasources: [],
|
||||||
hasCreateRights={true}
|
initialFavoriteDataSources: [],
|
||||||
hasWriteRights={true}
|
addFavoriteDatasource: jest.fn(),
|
||||||
hasExploreRights={true}
|
removeFavoriteDatasource: jest.fn(),
|
||||||
/>
|
toggleFavoriteDatasource: jest.fn(),
|
||||||
);
|
}));
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => {
|
||||||
|
const runtime = jest.requireActual('@grafana/runtime');
|
||||||
|
return {
|
||||||
|
...runtime,
|
||||||
|
useFavoriteDatasources: () => mockUseFavoriteDatasources(),
|
||||||
|
config: {
|
||||||
|
...runtime.config,
|
||||||
|
featureToggles: {
|
||||||
|
...runtime.config.featureToggles,
|
||||||
|
favoriteDatasources: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock the useQueryParams hook
|
||||||
|
const mockUpdateQueryParams = jest.fn();
|
||||||
|
const mockUseQueryParams = jest.fn(() => [{ starred: undefined }, mockUpdateQueryParams]);
|
||||||
|
|
||||||
|
jest.mock('app/core/hooks/useQueryParams', () => ({
|
||||||
|
useQueryParams: () => mockUseQueryParams(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const setup = (overrides: Partial<ViewProps> = {}) => {
|
||||||
|
const defaultProps = {
|
||||||
|
dataSources: getMockDataSources(3),
|
||||||
|
dataSourcesCount: 3,
|
||||||
|
isLoading: false,
|
||||||
|
hasCreateRights: true,
|
||||||
|
hasWriteRights: true,
|
||||||
|
hasExploreRights: true,
|
||||||
|
showFavoritesOnly: false,
|
||||||
|
handleFavoritesCheckboxChange: jest.fn(),
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
|
||||||
|
return render(<DataSourcesListView {...defaultProps} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('<DataSourcesList>', () => {
|
describe('<DataSourcesList>', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
mockUseQueryParams.mockReturnValue([{ starred: undefined }, mockUpdateQueryParams]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should render action bar', async () => {
|
it('should render action bar', async () => {
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
@ -41,4 +86,56 @@ describe('<DataSourcesList>', () => {
|
|||||||
expect(await screen.findByRole('heading', { name: 'dataSource-0' })).toBeInTheDocument();
|
expect(await screen.findByRole('heading', { name: 'dataSource-0' })).toBeInTheDocument();
|
||||||
expect(await screen.findByRole('link', { name: 'dataSource-0' })).toBeInTheDocument();
|
expect(await screen.findByRole('link', { name: 'dataSource-0' })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Favorites functionality', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
config.featureToggles.favoriteDatasources = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render favorites checkbox when feature toggle is enabled', async () => {
|
||||||
|
setup({ favoriteDataSources: mockUseFavoriteDatasources() });
|
||||||
|
|
||||||
|
const checkbox = await screen.findByRole('checkbox', { name: 'Starred' });
|
||||||
|
expect(checkbox).toBeInTheDocument();
|
||||||
|
expect(checkbox).not.toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render favorites checkbox when feature toggle is disabled', async () => {
|
||||||
|
config.featureToggles.favoriteDatasources = false;
|
||||||
|
|
||||||
|
setup();
|
||||||
|
|
||||||
|
expect(await screen.findByPlaceholderText('Search by name or type')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole('checkbox', { name: 'Starred' })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render favorites checkbox as checked when value is true', async () => {
|
||||||
|
setup({ showFavoritesOnly: true, favoriteDataSources: mockUseFavoriteDatasources() });
|
||||||
|
|
||||||
|
const checkbox = await screen.findByRole('checkbox', { name: 'Starred' });
|
||||||
|
expect(checkbox).toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter datasources to show only favorites when showFavoritesOnly is true', async () => {
|
||||||
|
// Mock the isFavoriteDatasource function to return true for specific datasources
|
||||||
|
const mockIsFavoriteDatasource = jest.fn((uid: string) => uid === 'uid-0' || uid === 'uid-2');
|
||||||
|
|
||||||
|
setup({
|
||||||
|
showFavoritesOnly: true,
|
||||||
|
favoriteDataSources: {
|
||||||
|
...mockUseFavoriteDatasources(),
|
||||||
|
isFavoriteDatasource: mockIsFavoriteDatasource,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should only show 2 datasources (uid-0 and uid-2) instead of all 3
|
||||||
|
const listItems = await screen.findAllByRole('listitem');
|
||||||
|
expect(listItems).toHaveLength(2);
|
||||||
|
|
||||||
|
// Verify the correct datasources are shown
|
||||||
|
expect(screen.getByRole('heading', { name: 'dataSource-0' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('heading', { name: 'dataSource-2' })).toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole('heading', { name: 'dataSource-1' })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { useLocation } from 'react-router-dom-v5-compat';
|
import { useLocation } from 'react-router-dom-v5-compat';
|
||||||
|
|
||||||
import { DataSourceSettings, GrafanaTheme2 } from '@grafana/data';
|
import { DataSourceSettings, GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Trans, t } from '@grafana/i18n';
|
import { Trans, t } from '@grafana/i18n';
|
||||||
import { config } from '@grafana/runtime';
|
import { config, useFavoriteDatasources, FavoriteDatasources } from '@grafana/runtime';
|
||||||
import { EmptyState, LinkButton, TextLink, useStyles2 } from '@grafana/ui';
|
import { EmptyState, LinkButton, TextLink, useStyles2 } from '@grafana/ui';
|
||||||
import { contextSrv } from 'app/core/core';
|
import { contextSrv } from 'app/core/core';
|
||||||
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
import { AccessControlAction } from 'app/types/accessControl';
|
import { AccessControlAction } from 'app/types/accessControl';
|
||||||
import { StoreState, useSelector } from 'app/types/store';
|
import { StoreState, useSelector } from 'app/types/store';
|
||||||
|
|
||||||
@ -20,6 +21,12 @@ import { DataSourcesListHeader } from './DataSourcesListHeader';
|
|||||||
|
|
||||||
export function DataSourcesList() {
|
export function DataSourcesList() {
|
||||||
const { isLoading } = useLoadDataSources();
|
const { isLoading } = useLoadDataSources();
|
||||||
|
const favoriteDataSources = useFavoriteDatasources();
|
||||||
|
const [queryParams, updateQueryParams] = useQueryParams();
|
||||||
|
const showFavoritesOnly = !!queryParams.starred;
|
||||||
|
const handleFavoritesCheckboxChange = (value: boolean) => {
|
||||||
|
updateQueryParams({ starred: value ? 'true' : undefined });
|
||||||
|
};
|
||||||
|
|
||||||
const dataSources = useSelector((state) => getDataSources(state.dataSources));
|
const dataSources = useSelector((state) => getDataSources(state.dataSources));
|
||||||
const dataSourcesCount = useSelector(({ dataSources }: StoreState) => getDataSourcesCount(dataSources));
|
const dataSourcesCount = useSelector(({ dataSources }: StoreState) => getDataSourcesCount(dataSources));
|
||||||
@ -35,6 +42,9 @@ export function DataSourcesList() {
|
|||||||
hasCreateRights={hasCreateRights}
|
hasCreateRights={hasCreateRights}
|
||||||
hasWriteRights={hasWriteRights}
|
hasWriteRights={hasWriteRights}
|
||||||
hasExploreRights={hasExploreRights}
|
hasExploreRights={hasExploreRights}
|
||||||
|
showFavoritesOnly={showFavoritesOnly}
|
||||||
|
handleFavoritesCheckboxChange={handleFavoritesCheckboxChange}
|
||||||
|
favoriteDataSources={favoriteDataSources}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -46,18 +56,40 @@ export type ViewProps = {
|
|||||||
hasCreateRights: boolean;
|
hasCreateRights: boolean;
|
||||||
hasWriteRights: boolean;
|
hasWriteRights: boolean;
|
||||||
hasExploreRights: boolean;
|
hasExploreRights: boolean;
|
||||||
|
showFavoritesOnly?: boolean;
|
||||||
|
handleFavoritesCheckboxChange?: (value: boolean) => void;
|
||||||
|
favoriteDataSources?: FavoriteDatasources;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function DataSourcesListView({
|
export function DataSourcesListView({
|
||||||
dataSources,
|
dataSources: allDataSources,
|
||||||
dataSourcesCount,
|
dataSourcesCount,
|
||||||
isLoading,
|
isLoading,
|
||||||
hasCreateRights,
|
hasCreateRights,
|
||||||
hasWriteRights,
|
hasWriteRights,
|
||||||
hasExploreRights,
|
hasExploreRights,
|
||||||
|
showFavoritesOnly,
|
||||||
|
handleFavoritesCheckboxChange,
|
||||||
|
favoriteDataSources,
|
||||||
}: ViewProps) {
|
}: ViewProps) {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const favoritesCheckbox =
|
||||||
|
favoriteDataSources?.enabled && handleFavoritesCheckboxChange && showFavoritesOnly !== undefined
|
||||||
|
? {
|
||||||
|
onChange: handleFavoritesCheckboxChange,
|
||||||
|
value: showFavoritesOnly,
|
||||||
|
label: t('datasources.list.starred', 'Starred'),
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
// Filter data sources based on favorites when enabled
|
||||||
|
const dataSources = useMemo(() => {
|
||||||
|
if (!showFavoritesOnly || !favoriteDataSources?.enabled) {
|
||||||
|
return allDataSources;
|
||||||
|
}
|
||||||
|
return allDataSources.filter((dataSource) => favoriteDataSources?.isFavoriteDatasource(dataSource.uid));
|
||||||
|
}, [allDataSources, showFavoritesOnly, favoriteDataSources]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
trackDataSourcesListViewed({
|
trackDataSourcesListViewed({
|
||||||
@ -111,7 +143,7 @@ export function DataSourcesListView({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* List Header */}
|
{/* List Header */}
|
||||||
<DataSourcesListHeader />
|
<DataSourcesListHeader filterCheckbox={favoritesCheckbox} />
|
||||||
|
|
||||||
{/* List */}
|
{/* List */}
|
||||||
{dataSources.length === 0 && !isLoading ? (
|
{dataSources.length === 0 && !isLoading ? (
|
||||||
|
@ -2,7 +2,7 @@ import { debounce } from 'lodash';
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import PageActionBar from 'app/core/components/PageActionBar/PageActionBar';
|
import PageActionBar, { FilterCheckbox } from 'app/core/components/PageActionBar/PageActionBar';
|
||||||
import { StoreState, useSelector, useDispatch } from 'app/types/store';
|
import { StoreState, useSelector, useDispatch } from 'app/types/store';
|
||||||
|
|
||||||
import { setDataSourcesSearchQuery, setIsSortAscending } from '../state/reducers';
|
import { setDataSourcesSearchQuery, setIsSortAscending } from '../state/reducers';
|
||||||
@ -20,8 +20,13 @@ const sortOptions = [
|
|||||||
{ label: 'Sort by Z–A', value: descendingSortValue },
|
{ label: 'Sort by Z–A', value: descendingSortValue },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function DataSourcesListHeader() {
|
export interface DataSourcesListHeaderProps {
|
||||||
|
filterCheckbox?: FilterCheckbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DataSourcesListHeader({ filterCheckbox }: DataSourcesListHeaderProps) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const debouncedTrackSearch = useMemo(
|
const debouncedTrackSearch = useMemo(
|
||||||
() =>
|
() =>
|
||||||
debounce((q) => {
|
debounce((q) => {
|
||||||
@ -54,6 +59,12 @@ export function DataSourcesListHeader() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageActionBar searchQuery={searchQuery} setSearchQuery={setSearchQuery} key="action-bar" sortPicker={sortPicker} />
|
<PageActionBar
|
||||||
|
searchQuery={searchQuery}
|
||||||
|
setSearchQuery={setSearchQuery}
|
||||||
|
key="action-bar"
|
||||||
|
sortPicker={sortPicker}
|
||||||
|
filterCheckbox={filterCheckbox}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6583,6 +6583,9 @@
|
|||||||
"hosted-graphite-prometheus-and-loki": "Hosted Graphite, Prometheus, and Loki"
|
"hosted-graphite-prometheus-and-loki": "Hosted Graphite, Prometheus, and Loki"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"list": {
|
||||||
|
"starred": "Starred"
|
||||||
|
},
|
||||||
"new-data-source-view": {
|
"new-data-source-view": {
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"placeholder-filter-by-name-or-type": "Filter by name or type"
|
"placeholder-filter-by-name-or-type": "Filter by name or type"
|
||||||
|
Reference in New Issue
Block a user