mirror of
https://github.com/grafana/grafana.git
synced 2026-09-05 04:40:13 -05:00
LibraryPanels: Adds folder filter to manage library panel page (#33560)
* LibraryPanels: Adds folder filter * Refactor: Adds folder filter to library search * Refactor: splits huge function into smaller functions * LibraryPanels: Adds Panels Page to Manage Folder tabs (#33618) * Chore: adds tests to LibraryPanelsSearch * Refactor: Adds reducer and tests * Chore: changes GrafanaThemeV2 * Refactor: pulls everything behind the feature toggle * Chore: removes clear icon from FolderFilter * Chore: adds filter to SortPicker * Refactor: using useAsync instead
This commit is contained in:
@@ -25,7 +25,7 @@ export const LibraryPanelsPage: FC<Props> = ({ navModel }) => {
|
||||
return (
|
||||
<Page navModel={navModel}>
|
||||
<Page.Contents>
|
||||
<LibraryPanelsSearch onClick={setSelected} showSecondaryActions showSort showFilter />
|
||||
<LibraryPanelsSearch onClick={setSelected} showSecondaryActions showSort showPanelFilter showFolderFilter />
|
||||
{selected ? <OpenLibraryPanelModal onDismiss={() => setSelected(undefined)} libraryPanel={selected} /> : null}
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { within } from '@testing-library/dom';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { PanelPluginMeta, PluginType } from '@grafana/data';
|
||||
|
||||
import { LibraryPanelsSearch, LibraryPanelsSearchProps } from './LibraryPanelsSearch';
|
||||
import * as api from '../../state/api';
|
||||
import { LibraryPanelSearchResult } from '../../types';
|
||||
import { backendSrv } from '../../../../core/services/backend_srv';
|
||||
import * as viztypepicker from '../../../dashboard/components/VizTypePicker/VizTypePicker';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...((jest.requireActual('@grafana/runtime') as unknown) as object),
|
||||
config: {
|
||||
panels: {
|
||||
timeseries: {
|
||||
info: { logos: { small: '' } },
|
||||
name: 'Time Series',
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('debounce-promise', () => {
|
||||
const debounce = (fn: any) => {
|
||||
const debounced = () =>
|
||||
Promise.resolve([
|
||||
{ label: 'General', value: { id: 0, title: 'General' } },
|
||||
{ label: 'Folder1', value: { id: 1, title: 'Folder1' } },
|
||||
{ label: 'Folder2', value: { id: 2, title: 'Folder2' } },
|
||||
]);
|
||||
return debounced;
|
||||
};
|
||||
|
||||
return debounce;
|
||||
});
|
||||
|
||||
async function getTestContext(
|
||||
propOverrides: Partial<LibraryPanelsSearchProps> = {},
|
||||
searchResult: LibraryPanelSearchResult = { libraryPanels: [], perPage: 40, page: 1, totalCount: 0 }
|
||||
) {
|
||||
jest.clearAllMocks();
|
||||
const pluginInfo: any = { logos: { small: '', large: '' } };
|
||||
const graph: PanelPluginMeta = {
|
||||
name: 'Graph',
|
||||
id: 'graph',
|
||||
info: pluginInfo,
|
||||
baseUrl: '',
|
||||
type: PluginType.panel,
|
||||
module: '',
|
||||
sort: 0,
|
||||
};
|
||||
const timeseries: PanelPluginMeta = {
|
||||
name: 'Time Series',
|
||||
id: 'timeseries',
|
||||
info: pluginInfo,
|
||||
baseUrl: '',
|
||||
type: PluginType.panel,
|
||||
module: '',
|
||||
sort: 1,
|
||||
};
|
||||
const getSpy = jest
|
||||
.spyOn(backendSrv, 'get')
|
||||
.mockResolvedValue({ sortOptions: [{ displaName: 'Desc', name: 'alpha-desc' }] });
|
||||
const getLibraryPanelsSpy = jest.spyOn(api, 'getLibraryPanels').mockResolvedValue(searchResult);
|
||||
const getAllPanelPluginMetaSpy = jest
|
||||
.spyOn(viztypepicker, 'getAllPanelPluginMeta')
|
||||
.mockReturnValue([graph, timeseries]);
|
||||
|
||||
const props: LibraryPanelsSearchProps = {
|
||||
onClick: jest.fn(),
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
const { rerender } = render(<LibraryPanelsSearch {...props} />);
|
||||
|
||||
await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalled());
|
||||
expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
return { rerender, getLibraryPanelsSpy, getSpy, getAllPanelPluginMetaSpy };
|
||||
}
|
||||
|
||||
describe('LibraryPanelsSearch', () => {
|
||||
describe('when mounted with default options', () => {
|
||||
it('should show input filter and library panels view', async () => {
|
||||
await getTestContext();
|
||||
|
||||
expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/no library panels found./i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('and user searches for library panel by name or description', () => {
|
||||
it('should call api with correct params', async () => {
|
||||
const { getLibraryPanelsSpy } = await getTestContext();
|
||||
getLibraryPanelsSpy.mockClear();
|
||||
|
||||
await userEvent.type(screen.getByPlaceholderText(/search by name/i), 'a');
|
||||
await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalled());
|
||||
expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1);
|
||||
expect(getLibraryPanelsSpy).toHaveBeenCalledWith({
|
||||
searchString: 'a',
|
||||
folderFilter: [],
|
||||
page: 0,
|
||||
panelFilter: [],
|
||||
perPage: 40,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when mounted with showSort', () => {
|
||||
it('should show input filter and library panels view and sort', async () => {
|
||||
await getTestContext({ showSort: true });
|
||||
|
||||
expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/no library panels found./i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/sort \(default a–z\)/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('and user changes sorting', () => {
|
||||
it('should call api with correct params', async () => {
|
||||
const { getLibraryPanelsSpy } = await getTestContext({ showSort: true });
|
||||
getLibraryPanelsSpy.mockClear();
|
||||
|
||||
await userEvent.type(screen.getByText(/sort \(default a–z\)/i), 'Desc{enter}');
|
||||
await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1));
|
||||
expect(getLibraryPanelsSpy).toHaveBeenCalledWith({
|
||||
searchString: '',
|
||||
sortDirection: 'alpha-desc',
|
||||
folderFilter: [],
|
||||
page: 0,
|
||||
panelFilter: [],
|
||||
perPage: 40,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when mounted with showPanelFilter', () => {
|
||||
it('should show input filter and library panels view and panel filter', async () => {
|
||||
await getTestContext({ showPanelFilter: true });
|
||||
|
||||
expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/no library panels found./i)).toBeInTheDocument();
|
||||
expect(screen.getByRole('textbox', { name: /panel type filter/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('and user changes panel filter', () => {
|
||||
it('should call api with correct params', async () => {
|
||||
const { getLibraryPanelsSpy } = await getTestContext({ showPanelFilter: true });
|
||||
getLibraryPanelsSpy.mockClear();
|
||||
|
||||
await userEvent.type(screen.getByRole('textbox', { name: /panel type filter/i }), 'Graph{enter}');
|
||||
await userEvent.type(screen.getByRole('textbox', { name: /panel type filter/i }), 'Time Series{enter}');
|
||||
await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1));
|
||||
expect(getLibraryPanelsSpy).toHaveBeenCalledWith({
|
||||
searchString: '',
|
||||
folderFilter: [],
|
||||
page: 0,
|
||||
panelFilter: ['graph', 'timeseries'],
|
||||
perPage: 40,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when mounted with showPanelFilter', () => {
|
||||
it('should show input filter and library panels view and folder filter', async () => {
|
||||
await getTestContext({ showFolderFilter: true });
|
||||
|
||||
expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/no library panels found./i)).toBeInTheDocument();
|
||||
expect(screen.getByRole('textbox', { name: /folder filter/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('and user changes folder filter', () => {
|
||||
it('should call api with correct params', async () => {
|
||||
const { getLibraryPanelsSpy } = await getTestContext({ showFolderFilter: true });
|
||||
getLibraryPanelsSpy.mockClear();
|
||||
|
||||
userEvent.click(screen.getByRole('textbox', { name: /folder filter/i }));
|
||||
await userEvent.type(screen.getByRole('textbox', { name: /folder filter/i }), '{enter}', {
|
||||
skipClick: true,
|
||||
});
|
||||
await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1));
|
||||
expect(getLibraryPanelsSpy).toHaveBeenCalledWith({
|
||||
searchString: '',
|
||||
folderFilter: ['0'],
|
||||
page: 0,
|
||||
panelFilter: [],
|
||||
perPage: 40,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when mounted without showSecondaryActions and there is one panel', () => {
|
||||
it('should show correct row and no delete button', async () => {
|
||||
await getTestContext(
|
||||
{},
|
||||
{
|
||||
page: 1,
|
||||
totalCount: 1,
|
||||
perPage: 40,
|
||||
libraryPanels: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Library Panel Name',
|
||||
uid: 'uid',
|
||||
description: 'Library Panel Description',
|
||||
folderId: 0,
|
||||
model: { type: 'timeseries', title: 'A title' },
|
||||
type: 'timeseries',
|
||||
orgId: 1,
|
||||
version: 1,
|
||||
meta: {
|
||||
canEdit: true,
|
||||
connectedDashboards: 0,
|
||||
created: '2021-01-01 12:00:00',
|
||||
createdBy: { id: 1, name: 'Admin', avatarUrl: '' },
|
||||
updated: '2021-01-01 12:00:00',
|
||||
updatedBy: { id: 1, name: 'Admin', avatarUrl: '' },
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
const card = () => screen.getByLabelText(/plugin visualization item time series/i);
|
||||
|
||||
expect(screen.queryByText(/no library panels found./i)).not.toBeInTheDocument();
|
||||
expect(card()).toBeInTheDocument();
|
||||
expect(within(card()).getByText(/library panel name/i)).toBeInTheDocument();
|
||||
expect(within(card()).getByText(/library panel description/i)).toBeInTheDocument();
|
||||
expect(within(card()).queryByLabelText(/delete button on panel type card/i)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when mounted with showSecondaryActions and there is one panel', () => {
|
||||
it('should show correct row and delete button', async () => {
|
||||
await getTestContext(
|
||||
{ showSecondaryActions: true },
|
||||
{
|
||||
page: 1,
|
||||
totalCount: 1,
|
||||
perPage: 40,
|
||||
libraryPanels: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Library Panel Name',
|
||||
uid: 'uid',
|
||||
description: 'Library Panel Description',
|
||||
folderId: 0,
|
||||
model: { type: 'timeseries', title: 'A title' },
|
||||
type: 'timeseries',
|
||||
orgId: 1,
|
||||
version: 1,
|
||||
meta: {
|
||||
canEdit: true,
|
||||
connectedDashboards: 0,
|
||||
created: '2021-01-01 12:00:00',
|
||||
createdBy: { id: 1, name: 'Admin', avatarUrl: '' },
|
||||
updated: '2021-01-01 12:00:00',
|
||||
updatedBy: { id: 1, name: 'Admin', avatarUrl: '' },
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
const card = () => screen.getByLabelText(/plugin visualization item time series/i);
|
||||
|
||||
expect(screen.queryByText(/no library panels found./i)).not.toBeInTheDocument();
|
||||
expect(card()).toBeInTheDocument();
|
||||
expect(within(card()).getByText(/library panel name/i)).toBeInTheDocument();
|
||||
expect(within(card()).getByText(/library panel description/i)).toBeInTheDocument();
|
||||
expect(within(card()).getByLabelText(/delete button on panel type card/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
+48
-14
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useReducer } from 'react';
|
||||
import { HorizontalGroup, useStyles2, VerticalGroup } from '@grafana/ui';
|
||||
import { GrafanaTheme2, PanelPluginMeta, SelectableValue } from '@grafana/data';
|
||||
import { css } from '@emotion/css';
|
||||
@@ -8,6 +8,16 @@ import { PanelTypeFilter } from '../../../../core/components/PanelTypeFilter/Pan
|
||||
import { LibraryPanelsView } from '../LibraryPanelsView/LibraryPanelsView';
|
||||
import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../core/constants';
|
||||
import { LibraryPanelDTO } from '../../types';
|
||||
import { FolderFilter } from '../../../../core/components/FolderFilter/FolderFilter';
|
||||
import { FolderInfo } from '../../../../types';
|
||||
import {
|
||||
folderFilterChanged,
|
||||
initialLibraryPanelsSearchState,
|
||||
libraryPanelsSearchReducer,
|
||||
panelFilterChanged,
|
||||
searchChanged,
|
||||
sortChanged,
|
||||
} from './reducer';
|
||||
|
||||
export enum LibraryPanelsSearchVariant {
|
||||
Tight = 'tight',
|
||||
@@ -18,9 +28,11 @@ export interface LibraryPanelsSearchProps {
|
||||
onClick: (panel: LibraryPanelDTO) => void;
|
||||
variant?: LibraryPanelsSearchVariant;
|
||||
showSort?: boolean;
|
||||
showFilter?: boolean;
|
||||
showPanelFilter?: boolean;
|
||||
showFolderFilter?: boolean;
|
||||
showSecondaryActions?: boolean;
|
||||
currentPanelId?: string;
|
||||
currentFolderId?: number;
|
||||
perPage?: number;
|
||||
}
|
||||
|
||||
@@ -28,26 +40,44 @@ export const LibraryPanelsSearch = ({
|
||||
onClick,
|
||||
variant = LibraryPanelsSearchVariant.Spacious,
|
||||
currentPanelId,
|
||||
currentFolderId,
|
||||
perPage = DEFAULT_PER_PAGE_PAGINATION,
|
||||
showFilter = false,
|
||||
showPanelFilter = false,
|
||||
showFolderFilter = false,
|
||||
showSort = false,
|
||||
showSecondaryActions = false,
|
||||
}: LibraryPanelsSearchProps): JSX.Element => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [sortDirection, setSortDirection] = useState<string | undefined>(undefined);
|
||||
const [panelFilter, setPanelFilter] = useState<string[]>([]);
|
||||
const styles = useStyles2(getStyles);
|
||||
const onSortChange = useCallback((sort: SelectableValue<string>) => setSortDirection(sort.value), []);
|
||||
const onFilterChange = useCallback((plugins: PanelPluginMeta[]) => setPanelFilter(plugins.map((p) => p.id)), []);
|
||||
const [{ sortDirection, panelFilter, folderFilter, searchQuery }, dispatch] = useReducer(libraryPanelsSearchReducer, {
|
||||
...initialLibraryPanelsSearchState,
|
||||
folderFilter: currentFolderId ? [currentFolderId.toString(10)] : [],
|
||||
});
|
||||
const onFilterChange = (searchString: string) => dispatch(searchChanged(searchString));
|
||||
const onSortChange = (sorting: SelectableValue<string>) => dispatch(sortChanged(sorting));
|
||||
const onFolderFilterChange = (folders: FolderInfo[]) => dispatch(folderFilterChanged(folders));
|
||||
const onPanelFilterChange = (plugins: PanelPluginMeta[]) => dispatch(panelFilterChanged(plugins));
|
||||
|
||||
if (variant === LibraryPanelsSearchVariant.Spacious) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<VerticalGroup spacing="lg">
|
||||
<FilterInput value={searchQuery} onChange={setSearchQuery} placeholder={'Search by name'} width={0} />
|
||||
<HorizontalGroup spacing="sm" justify={showSort && showFilter ? 'space-between' : 'flex-end'}>
|
||||
{showSort && <SortPicker value={sortDirection} onChange={onSortChange} />}
|
||||
{showFilter && <PanelTypeFilter onChange={onFilterChange} />}
|
||||
<FilterInput
|
||||
value={searchQuery}
|
||||
onChange={onFilterChange}
|
||||
placeholder={'Search by name or description'}
|
||||
width={0}
|
||||
/>
|
||||
<HorizontalGroup
|
||||
spacing="sm"
|
||||
justify={(showSort && showPanelFilter) || showFolderFilter ? 'space-between' : 'flex-end'}
|
||||
>
|
||||
{showSort && (
|
||||
<SortPicker value={sortDirection} onChange={onSortChange} filter={['alpha-asc', 'alpha-desc']} />
|
||||
)}
|
||||
<HorizontalGroup spacing="sm" justify={showFolderFilter && showPanelFilter ? 'space-between' : 'flex-end'}>
|
||||
{showFolderFilter && <FolderFilter onChange={onFolderFilterChange} />}
|
||||
{showPanelFilter && <PanelTypeFilter onChange={onPanelFilterChange} />}
|
||||
</HorizontalGroup>
|
||||
</HorizontalGroup>
|
||||
<div className={styles.libraryPanelsView}>
|
||||
<LibraryPanelsView
|
||||
@@ -55,6 +85,7 @@ export const LibraryPanelsSearch = ({
|
||||
searchString={searchQuery}
|
||||
sortDirection={sortDirection}
|
||||
panelFilter={panelFilter}
|
||||
folderFilter={folderFilter}
|
||||
currentPanelId={currentPanelId}
|
||||
showSecondaryActions={showSecondaryActions}
|
||||
perPage={perPage}
|
||||
@@ -70,11 +101,12 @@ export const LibraryPanelsSearch = ({
|
||||
<VerticalGroup spacing="xs">
|
||||
<div className={styles.buttonRow}>
|
||||
<div className={styles.tightFilter}>
|
||||
<FilterInput value={searchQuery} onChange={setSearchQuery} placeholder={'Search by name'} width={0} />
|
||||
<FilterInput value={searchQuery} onChange={onFilterChange} placeholder={'Search by name'} width={0} />
|
||||
</div>
|
||||
<div className={styles.tightSortFilter}>
|
||||
{showSort && <SortPicker value={sortDirection} onChange={onSortChange} />}
|
||||
{showFilter && <PanelTypeFilter onChange={onFilterChange} />}
|
||||
{showFolderFilter && <FolderFilter onChange={onFolderFilterChange} maxMenuHeight={200} />}
|
||||
{showPanelFilter && <PanelTypeFilter onChange={onPanelFilterChange} maxMenuHeight={200} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.libraryPanelsView}>
|
||||
@@ -83,6 +115,7 @@ export const LibraryPanelsSearch = ({
|
||||
searchString={searchQuery}
|
||||
sortDirection={sortDirection}
|
||||
panelFilter={panelFilter}
|
||||
folderFilter={folderFilter}
|
||||
currentPanelId={currentPanelId}
|
||||
showSecondaryActions={showSecondaryActions}
|
||||
perPage={perPage}
|
||||
@@ -99,6 +132,7 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
padding: ${theme.spacing(1)};
|
||||
min-height: 400px;
|
||||
`,
|
||||
buttonRow: css`
|
||||
display: flex;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { reducerTester } from '../../../../../test/core/redux/reducerTester';
|
||||
import {
|
||||
folderFilterChanged,
|
||||
initialLibraryPanelsSearchState,
|
||||
libraryPanelsSearchReducer,
|
||||
LibraryPanelsSearchState,
|
||||
panelFilterChanged,
|
||||
searchChanged,
|
||||
sortChanged,
|
||||
} from './reducer';
|
||||
|
||||
describe('libraryPanelsSearchReducer', () => {
|
||||
describe('when searchChanged is dispatched', () => {
|
||||
it('then state should be correct', () => {
|
||||
reducerTester<LibraryPanelsSearchState>()
|
||||
.givenReducer(libraryPanelsSearchReducer, {
|
||||
...initialLibraryPanelsSearchState,
|
||||
})
|
||||
.whenActionIsDispatched(searchChanged('searching for'))
|
||||
.thenStateShouldEqual({
|
||||
...initialLibraryPanelsSearchState,
|
||||
searchQuery: 'searching for',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when sortChanged is dispatched', () => {
|
||||
it('then state should be correct', () => {
|
||||
reducerTester<LibraryPanelsSearchState>()
|
||||
.givenReducer(libraryPanelsSearchReducer, {
|
||||
...initialLibraryPanelsSearchState,
|
||||
})
|
||||
.whenActionIsDispatched(sortChanged({ label: 'Ascending', value: 'asc' }))
|
||||
.thenStateShouldEqual({
|
||||
...initialLibraryPanelsSearchState,
|
||||
sortDirection: 'asc',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when panelFilterChanged is dispatched', () => {
|
||||
it('then state should be correct', () => {
|
||||
const plugins: any = [
|
||||
{ id: 'graph', name: 'Graph' },
|
||||
{ id: 'timeseries', name: 'Time Series' },
|
||||
];
|
||||
reducerTester<LibraryPanelsSearchState>()
|
||||
.givenReducer(libraryPanelsSearchReducer, {
|
||||
...initialLibraryPanelsSearchState,
|
||||
})
|
||||
.whenActionIsDispatched(panelFilterChanged(plugins))
|
||||
.thenStateShouldEqual({
|
||||
...initialLibraryPanelsSearchState,
|
||||
panelFilter: ['graph', 'timeseries'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when folderFilterChanged is dispatched', () => {
|
||||
it('then state should be correct', () => {
|
||||
const folders: any = [
|
||||
{ id: 0, name: 'General' },
|
||||
{ id: 1, name: 'Folder' },
|
||||
];
|
||||
reducerTester<LibraryPanelsSearchState>()
|
||||
.givenReducer(libraryPanelsSearchReducer, {
|
||||
...initialLibraryPanelsSearchState,
|
||||
})
|
||||
.whenActionIsDispatched(folderFilterChanged(folders))
|
||||
.thenStateShouldEqual({
|
||||
...initialLibraryPanelsSearchState,
|
||||
folderFilter: ['0', '1'],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { AnyAction } from 'redux';
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { PanelPluginMeta, SelectableValue } from '@grafana/data';
|
||||
|
||||
import { FolderInfo } from '../../../../types';
|
||||
|
||||
export interface LibraryPanelsSearchState {
|
||||
searchQuery: string;
|
||||
sortDirection?: string;
|
||||
panelFilter: string[];
|
||||
folderFilter: string[];
|
||||
}
|
||||
|
||||
export const initialLibraryPanelsSearchState: LibraryPanelsSearchState = {
|
||||
searchQuery: '',
|
||||
panelFilter: [],
|
||||
folderFilter: [],
|
||||
sortDirection: undefined,
|
||||
};
|
||||
|
||||
export const searchChanged = createAction<string>('libraryPanels/search/searchChanged');
|
||||
export const sortChanged = createAction<SelectableValue<string>>('libraryPanels/search/sortChanged');
|
||||
export const panelFilterChanged = createAction<PanelPluginMeta[]>('libraryPanels/search/panelFilterChanged');
|
||||
export const folderFilterChanged = createAction<FolderInfo[]>('libraryPanels/search/folderFilterChanged');
|
||||
|
||||
export const libraryPanelsSearchReducer = (state: LibraryPanelsSearchState, action: AnyAction) => {
|
||||
if (searchChanged.match(action)) {
|
||||
return { ...state, searchQuery: action.payload };
|
||||
}
|
||||
|
||||
if (sortChanged.match(action)) {
|
||||
return { ...state, sortDirection: action.payload.value };
|
||||
}
|
||||
|
||||
if (panelFilterChanged.match(action)) {
|
||||
return { ...state, panelFilter: action.payload.map((p) => p.id) };
|
||||
}
|
||||
|
||||
if (folderFilterChanged.match(action)) {
|
||||
return { ...state, folderFilter: action.payload.map((f) => String(f.id!)) };
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
+12
-2
@@ -17,6 +17,7 @@ interface LibraryPanelViewProps {
|
||||
searchString: string;
|
||||
sortDirection?: string;
|
||||
panelFilter?: string[];
|
||||
folderFilter?: string[];
|
||||
perPage?: number;
|
||||
}
|
||||
|
||||
@@ -26,6 +27,7 @@ export const LibraryPanelsView: React.FC<LibraryPanelViewProps> = ({
|
||||
searchString,
|
||||
sortDirection,
|
||||
panelFilter,
|
||||
folderFilter,
|
||||
showSecondaryActions,
|
||||
currentPanelId: currentPanel,
|
||||
perPage: propsPerPage = 40,
|
||||
@@ -43,10 +45,18 @@ export const LibraryPanelsView: React.FC<LibraryPanelViewProps> = ({
|
||||
useDebounce(
|
||||
() =>
|
||||
asyncDispatch(
|
||||
searchForLibraryPanels({ searchString, sortDirection, panelFilter, page, perPage, currentPanelId })
|
||||
searchForLibraryPanels({
|
||||
searchString,
|
||||
sortDirection,
|
||||
panelFilter,
|
||||
folderFilter,
|
||||
page,
|
||||
perPage,
|
||||
currentPanelId,
|
||||
})
|
||||
),
|
||||
300,
|
||||
[searchString, sortDirection, panelFilter, page, asyncDispatch]
|
||||
[searchString, sortDirection, panelFilter, folderFilter, page, asyncDispatch]
|
||||
);
|
||||
const onDelete = ({ uid }: LibraryPanelDTO) =>
|
||||
asyncDispatch(deleteLibraryPanel(uid, { searchString, page, perPage }));
|
||||
|
||||
@@ -13,6 +13,7 @@ interface SearchArgs {
|
||||
searchString: string;
|
||||
sortDirection?: string;
|
||||
panelFilter?: string[];
|
||||
folderFilter?: string[];
|
||||
currentPanelId?: string;
|
||||
}
|
||||
|
||||
@@ -27,6 +28,7 @@ export function searchForLibraryPanels(args: SearchArgs): DispatchResult {
|
||||
excludeUid: args.currentPanelId,
|
||||
sortDirection: args.sortDirection,
|
||||
panelFilter: args.panelFilter,
|
||||
folderFilter: args.folderFilter,
|
||||
})
|
||||
).pipe(
|
||||
mergeMap(({ perPage, libraryPanels, page, totalCount }) =>
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface GetLibraryPanelsOptions {
|
||||
excludeUid?: string;
|
||||
sortDirection?: string;
|
||||
panelFilter?: string[];
|
||||
folderFilter?: string[];
|
||||
}
|
||||
|
||||
export async function getLibraryPanels({
|
||||
@@ -18,11 +19,13 @@ export async function getLibraryPanels({
|
||||
excludeUid = '',
|
||||
sortDirection = '',
|
||||
panelFilter = [],
|
||||
folderFilter = [],
|
||||
}: GetLibraryPanelsOptions = {}): Promise<LibraryPanelSearchResult> {
|
||||
const params = new URLSearchParams();
|
||||
params.append('searchString', searchString);
|
||||
params.append('sortDirection', sortDirection);
|
||||
params.append('panelFilter', panelFilter.join(','));
|
||||
params.append('folderFilter', folderFilter.join(','));
|
||||
params.append('excludeUid', excludeUid);
|
||||
params.append('perPage', perPage.toString(10));
|
||||
params.append('page', page.toString(10));
|
||||
|
||||
Reference in New Issue
Block a user