Search/migrate search filter actions (#23133)

* Search: Initial setup

* Search: Use icon prop

* Search: Add button variants

* Search: Enable toggle all

* Search: Fix starred filter

* Search: update tests

* Search: Enable filters

* Search: Use emotion styling

* Search: Enable dashboard deleting

* Search: Enable dashboard moving

* Search: Update tests

* Search: Add SearchResultsFilter.test.tsx

* Search: Tweak types

* Search: Remove onReset

* Search: Remove redundant fragment

* Search: Use HorizontalGroup

* Search: Alight top checkbox
This commit is contained in:
Alex Khomenko
2020-04-02 14:07:31 +03:00
committed by GitHub
parent 9c9f6f168b
commit ce3a1fc56c
7 changed files with 243 additions and 67 deletions

View File

@@ -0,0 +1,97 @@
import React from 'react';
import { mount, shallow } from 'enzyme';
import { SearchResultsFilter, Props } from './SearchResultsFilter';
const noop = jest.fn();
const findBtnByText = (wrapper: any, text: string) =>
wrapper.findWhere((c: any) => c.name() === 'Button' && c.text() === text);
const setup = (propOverrides?: Partial<Props>, renderMethod = shallow) => {
const props: Props = {
//@ts-ignore
allChecked: false,
canDelete: false,
canMove: false,
deleteItem: noop,
moveTo: noop,
onSelectAllChanged: noop,
onStarredFilterChange: noop,
onTagFilterChange: noop,
selectedStarredFilter: 'starred',
selectedTagFilter: 'tag',
tagFilterOptions: [],
};
Object.assign(props, propOverrides);
const wrapper = renderMethod(<SearchResultsFilter {...props} />);
const instance = wrapper.instance();
return {
wrapper,
instance,
};
};
describe('SearchResultsFilter', () => {
it('should render "filter by starred" and "filter by tag" filters by default', () => {
const { wrapper } = setup();
expect(wrapper.find({ placeholder: 'Filter by starred' })).toHaveLength(1);
expect(wrapper.find({ placeholder: 'Filter by tag' })).toHaveLength(1);
expect(findBtnByText(wrapper, 'Move')).toHaveLength(0);
expect(findBtnByText(wrapper, 'Delete')).toHaveLength(0);
});
it('should render Move and Delete buttons when canDelete is true', () => {
const { wrapper } = setup({ canDelete: true });
expect(wrapper.find({ placeholder: 'Filter by starred' })).toHaveLength(0);
expect(wrapper.find({ placeholder: 'Filter by tag' })).toHaveLength(0);
expect(findBtnByText(wrapper, 'Move')).toHaveLength(1);
expect(findBtnByText(wrapper, 'Delete')).toHaveLength(1);
});
it('should render Move and Delete buttons when canMove is true', () => {
const { wrapper } = setup({ canMove: true });
expect(wrapper.find({ placeholder: 'Filter by starred' })).toHaveLength(0);
expect(wrapper.find({ placeholder: 'Filter by tag' })).toHaveLength(0);
expect(findBtnByText(wrapper, 'Move')).toHaveLength(1);
expect(findBtnByText(wrapper, 'Delete')).toHaveLength(1);
});
it('should be called with proper filter option when "filter by starred" is changed', () => {
const mockFilterStarred = jest.fn();
const option = { value: true, label: 'Yes' };
//@ts-ignore
const { wrapper } = setup({ onStarredFilterChange: mockFilterStarred }, mount);
wrapper
.find({ placeholder: 'Filter by starred' })
.at(0)
.prop('onChange')(option);
expect(mockFilterStarred).toHaveBeenCalledTimes(1);
expect(mockFilterStarred).toHaveBeenCalledWith(option);
});
it('should be called with proper filter option when "filter by tags" is changed', () => {
const mockFilterByTags = jest.fn();
const tags = [
{ value: 'tag1', label: 'Tag 1' },
{ value: 'tag2', label: 'Tag 2' },
];
//@ts-ignore
const { wrapper } = setup({ onTagFilterChange: mockFilterByTags, tagFilterOptions: tags }, mount);
wrapper
.find({ placeholder: 'Filter by tag' })
.at(0)
.prop('onChange')(tags[0]);
expect(mockFilterByTags).toHaveBeenCalledTimes(1);
expect(mockFilterByTags).toHaveBeenCalledWith(tags[0]);
});
it('should call "onSelectAllChanged" when checkbox is changed', () => {
const mockSelectAll = jest.fn();
const { wrapper } = setup({ onSelectAllChanged: mockSelectAll });
wrapper.find('Checkbox').simulate('change');
expect(mockSelectAll).toHaveBeenCalledTimes(1);
});
});

View File

@@ -0,0 +1,93 @@
import React, { FC } from 'react';
import { css } from 'emotion';
import { Button, Forms, stylesFactory, useTheme, HorizontalGroup } from '@grafana/ui';
import { GrafanaTheme, SelectableValue } from '@grafana/data';
type onSelectChange = (value: SelectableValue) => void;
export interface Props {
allChecked?: boolean;
canDelete?: boolean;
canMove?: boolean;
deleteItem: () => void;
moveTo: () => void;
onSelectAllChanged: any;
onStarredFilterChange: onSelectChange;
onTagFilterChange: onSelectChange;
selectedStarredFilter: string;
selectedTagFilter: string;
tagFilterOptions: SelectableValue[];
}
const starredFilterOptions = [
{ label: 'Yes', value: true },
{ label: 'No', value: false },
];
export const SearchResultsFilter: FC<Props> = ({
allChecked,
canDelete,
canMove,
deleteItem,
moveTo,
onSelectAllChanged,
onStarredFilterChange,
onTagFilterChange,
selectedStarredFilter,
selectedTagFilter,
tagFilterOptions,
}) => {
const showActions = canDelete || canMove;
const theme = useTheme();
const styles = getStyles(theme);
return (
<div className={styles.wrapper}>
<Forms.Checkbox value={allChecked} onChange={onSelectAllChanged} />
{showActions ? (
<HorizontalGroup spacing="md">
<Button disabled={!canMove} onClick={moveTo} icon="fa fa-exchange" variant="secondary">
Move
</Button>
<Button disabled={!canDelete} onClick={deleteItem} icon="fa fa-trash" variant="destructive">
Delete
</Button>
</HorizontalGroup>
) : (
<HorizontalGroup spacing="md">
<Forms.Select
size="sm"
placeholder="Filter by starred"
key={selectedStarredFilter}
options={starredFilterOptions}
onChange={onStarredFilterChange}
/>
<Forms.Select
size="sm"
placeholder="Filter by tag"
key={selectedTagFilter}
options={tagFilterOptions}
onChange={onTagFilterChange}
/>
</HorizontalGroup>
)}
</div>
);
};
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
wrapper: css`
height: 35px;
display: flex;
justify-content: space-between;
align-items: center;
label {
height: 20px;
margin-left: 8px;
}
`,
};
});

View File

@@ -2,4 +2,5 @@ export { SearchResults } from './components/SearchResults';
export { SearchField } from './components/SearchField';
export { SearchItem } from './components/SearchItem';
export { SearchCheckbox } from './components/SearchCheckbox';
export { SearchResultsFilter } from './components/SearchResultsFilter';
export * from './types';