-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[App Search] Create Curation view/functionality (#92560)
* Add server route and logic listener * [Misc] Remove 'Set' from 'deleteCurationSet' - to match createCuration - IMO, this language isn't necessary if we're splitting up Curations and CurationLogic - the context is fairly evident within a smaller and more modular logic file * Add CurationQueries component + accompanying CurationQueriesLogic & CurationQuery row * Add CurationCreation view
- Loading branch information
Constance
authored
Feb 25, 2021
1 parent
fd348d3
commit 0198607
Showing
21 changed files
with
714 additions
and
19 deletions.
There are no files selected for viewing
3 changes: 3 additions & 0 deletions
3
...cations/app_search/components/curations/components/curation_queries/curation_queries.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.curationQueryRow { | ||
margin-bottom: $euiSizeXS; | ||
} |
102 changes: 102 additions & 0 deletions
102
...ons/app_search/components/curations/components/curation_queries/curation_queries.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { setMockActions, setMockValues } from '../../../../../__mocks__'; | ||
|
||
import React from 'react'; | ||
|
||
import { shallow } from 'enzyme'; | ||
|
||
import { CurationQuery } from './curation_query'; | ||
|
||
import { CurationQueries } from './'; | ||
|
||
describe('CurationQueries', () => { | ||
const props = { | ||
queries: ['a', 'b', 'c'], | ||
onSubmit: jest.fn(), | ||
}; | ||
const values = { | ||
queries: ['a', 'b', 'c'], | ||
hasEmptyQueries: false, | ||
hasOnlyOneQuery: false, | ||
}; | ||
const actions = { | ||
addQuery: jest.fn(), | ||
editQuery: jest.fn(), | ||
deleteQuery: jest.fn(), | ||
}; | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
setMockValues(values); | ||
setMockActions(actions); | ||
}); | ||
|
||
it('renders a CurationQuery row for each query', () => { | ||
const wrapper = shallow(<CurationQueries {...props} />); | ||
|
||
expect(wrapper.find(CurationQuery)).toHaveLength(3); | ||
expect(wrapper.find(CurationQuery).at(0).prop('queryValue')).toEqual('a'); | ||
expect(wrapper.find(CurationQuery).at(1).prop('queryValue')).toEqual('b'); | ||
expect(wrapper.find(CurationQuery).at(2).prop('queryValue')).toEqual('c'); | ||
}); | ||
|
||
it('calls editQuery when the CurationQuery value changes', () => { | ||
const wrapper = shallow(<CurationQueries {...props} />); | ||
wrapper.find(CurationQuery).at(0).simulate('change', 'new query value'); | ||
|
||
expect(actions.editQuery).toHaveBeenCalledWith(0, 'new query value'); | ||
}); | ||
|
||
it('calls deleteQuery when the CurationQuery calls onDelete', () => { | ||
const wrapper = shallow(<CurationQueries {...props} />); | ||
wrapper.find(CurationQuery).at(2).simulate('delete'); | ||
|
||
expect(actions.deleteQuery).toHaveBeenCalledWith(2); | ||
}); | ||
|
||
it('calls addQuery when the Add Query button is clicked', () => { | ||
const wrapper = shallow(<CurationQueries {...props} />); | ||
wrapper.find('[data-test-subj="addCurationQueryButton"]').simulate('click'); | ||
|
||
expect(actions.addQuery).toHaveBeenCalled(); | ||
}); | ||
|
||
it('disables the add button if any query fields are empty', () => { | ||
setMockValues({ | ||
...values, | ||
queries: ['a', '', 'c'], | ||
hasEmptyQueries: true, | ||
}); | ||
const wrapper = shallow(<CurationQueries {...props} />); | ||
const button = wrapper.find('[data-test-subj="addCurationQueryButton"]'); | ||
|
||
expect(button.prop('isDisabled')).toEqual(true); | ||
}); | ||
|
||
it('calls the passed onSubmit callback when the submit button is clicked', () => { | ||
setMockValues({ ...values, queries: ['some query'] }); | ||
const wrapper = shallow(<CurationQueries {...props} />); | ||
wrapper.find('[data-test-subj="submitCurationQueriesButton"]').simulate('click'); | ||
|
||
expect(props.onSubmit).toHaveBeenCalledWith(['some query']); | ||
}); | ||
|
||
it('disables the submit button if no query fields have been filled', () => { | ||
setMockValues({ | ||
...values, | ||
queries: [''], | ||
hasOnlyOneQuery: true, | ||
hasEmptyQueries: true, | ||
}); | ||
const wrapper = shallow(<CurationQueries {...props} />); | ||
const button = wrapper.find('[data-test-subj="submitCurationQueriesButton"]'); | ||
|
||
expect(button.prop('isDisabled')).toEqual(true); | ||
}); | ||
}); |
72 changes: 72 additions & 0 deletions
72
...ications/app_search/components/curations/components/curation_queries/curation_queries.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import React from 'react'; | ||
|
||
import { useValues, useActions } from 'kea'; | ||
|
||
import { EuiButton, EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; | ||
import { i18n } from '@kbn/i18n'; | ||
|
||
import { Curation } from '../../types'; | ||
|
||
import { CurationQueriesLogic } from './curation_queries_logic'; | ||
import { CurationQuery } from './curation_query'; | ||
import { filterEmptyQueries } from './utils'; | ||
import './curation_queries.scss'; | ||
|
||
interface Props { | ||
queries: Curation['queries']; | ||
onSubmit(queries: Curation['queries']): void; | ||
submitButtonText?: string; | ||
} | ||
|
||
export const CurationQueries: React.FC<Props> = ({ | ||
queries: initialQueries, | ||
onSubmit, | ||
submitButtonText = i18n.translate('xpack.enterpriseSearch.actions.continue', { | ||
defaultMessage: 'Continue', | ||
}), | ||
}) => { | ||
const logic = CurationQueriesLogic({ queries: initialQueries }); | ||
const { queries, hasEmptyQueries, hasOnlyOneQuery } = useValues(logic); | ||
const { addQuery, editQuery, deleteQuery } = useActions(logic); | ||
|
||
return ( | ||
<> | ||
{queries.map((query: string, index) => ( | ||
<CurationQuery | ||
key={`query-${index}`} | ||
queryValue={query} | ||
onChange={(newValue) => editQuery(index, newValue)} | ||
onDelete={() => deleteQuery(index)} | ||
disableDelete={hasOnlyOneQuery} | ||
/> | ||
))} | ||
<EuiButtonEmpty | ||
size="s" | ||
iconType="plusInCircle" | ||
onClick={addQuery} | ||
isDisabled={hasEmptyQueries} | ||
data-test-subj="addCurationQueryButton" | ||
> | ||
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.addQueryButtonLabel', { | ||
defaultMessage: 'Add query', | ||
})} | ||
</EuiButtonEmpty> | ||
<EuiSpacer /> | ||
<EuiButton | ||
fill | ||
isDisabled={hasOnlyOneQuery && hasEmptyQueries} | ||
onClick={() => onSubmit(filterEmptyQueries(queries))} | ||
data-test-subj="submitCurationQueriesButton" | ||
> | ||
{submitButtonText} | ||
</EuiButton> | ||
</> | ||
); | ||
}; |
98 changes: 98 additions & 0 deletions
98
...pp_search/components/curations/components/curation_queries/curation_queries_logic.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { resetContext } from 'kea'; | ||
|
||
import { CurationQueriesLogic } from './curation_queries_logic'; | ||
|
||
describe('CurationQueriesLogic', () => { | ||
const MOCK_QUERIES = ['a', 'b', 'c']; | ||
|
||
const DEFAULT_PROPS = { queries: MOCK_QUERIES }; | ||
const DEFAULT_VALUES = { | ||
queries: MOCK_QUERIES, | ||
hasEmptyQueries: false, | ||
hasOnlyOneQuery: false, | ||
}; | ||
|
||
const mount = (props = {}) => { | ||
CurationQueriesLogic({ ...DEFAULT_PROPS, ...props }); | ||
CurationQueriesLogic.mount(); | ||
}; | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
resetContext({}); | ||
}); | ||
|
||
it('has expected default values passed from props', () => { | ||
mount(); | ||
expect(CurationQueriesLogic.values).toEqual(DEFAULT_VALUES); | ||
}); | ||
|
||
describe('actions', () => { | ||
afterEach(() => { | ||
// Should not mutate the original array | ||
expect(CurationQueriesLogic.values.queries).not.toBe(MOCK_QUERIES); // Would fail if we did not clone a new array | ||
}); | ||
|
||
describe('addQuery', () => { | ||
it('appends an empty string to the queries array', () => { | ||
mount(); | ||
CurationQueriesLogic.actions.addQuery(); | ||
|
||
expect(CurationQueriesLogic.values).toEqual({ | ||
...DEFAULT_VALUES, | ||
hasEmptyQueries: true, | ||
queries: ['a', 'b', 'c', ''], | ||
}); | ||
}); | ||
}); | ||
|
||
describe('deleteQuery', () => { | ||
it('deletes the query string at the specified array index', () => { | ||
mount(); | ||
CurationQueriesLogic.actions.deleteQuery(1); | ||
|
||
expect(CurationQueriesLogic.values).toEqual({ | ||
...DEFAULT_VALUES, | ||
queries: ['a', 'c'], | ||
}); | ||
}); | ||
}); | ||
|
||
describe('editQuery', () => { | ||
it('edits the query string at the specified array index', () => { | ||
mount(); | ||
CurationQueriesLogic.actions.editQuery(2, 'z'); | ||
|
||
expect(CurationQueriesLogic.values).toEqual({ | ||
...DEFAULT_VALUES, | ||
queries: ['a', 'b', 'z'], | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('selectors', () => { | ||
describe('hasEmptyQueries', () => { | ||
it('returns true if queries has any empty strings', () => { | ||
mount({ queries: ['', '', ''] }); | ||
|
||
expect(CurationQueriesLogic.values.hasEmptyQueries).toEqual(true); | ||
}); | ||
}); | ||
|
||
describe('hasOnlyOneQuery', () => { | ||
it('returns true if queries only has one item', () => { | ||
mount({ queries: ['test'] }); | ||
|
||
expect(CurationQueriesLogic.values.hasOnlyOneQuery).toEqual(true); | ||
}); | ||
}); | ||
}); | ||
}); |
53 changes: 53 additions & 0 deletions
53
...ons/app_search/components/curations/components/curation_queries/curation_queries_logic.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { kea, MakeLogicType } from 'kea'; | ||
|
||
interface CurationQueriesValues { | ||
queries: string[]; | ||
hasEmptyQueries: boolean; | ||
hasOnlyOneQuery: boolean; | ||
} | ||
|
||
interface CurationQueriesActions { | ||
addQuery(): void; | ||
deleteQuery(indexToDelete: number): { indexToDelete: number }; | ||
editQuery(index: number, newQueryValue: string): { index: number; newQueryValue: string }; | ||
} | ||
|
||
export const CurationQueriesLogic = kea< | ||
MakeLogicType<CurationQueriesValues, CurationQueriesActions> | ||
>({ | ||
path: ['enterprise_search', 'app_search', 'curation_queries_logic'], | ||
actions: () => ({ | ||
addQuery: true, | ||
deleteQuery: (indexToDelete) => ({ indexToDelete }), | ||
editQuery: (index, newQueryValue) => ({ index, newQueryValue }), | ||
}), | ||
reducers: ({ props }) => ({ | ||
queries: [ | ||
props.queries, | ||
{ | ||
addQuery: (state) => [...state, ''], | ||
deleteQuery: (state, { indexToDelete }) => { | ||
const newState = [...state]; | ||
newState.splice(indexToDelete, 1); | ||
return newState; | ||
}, | ||
editQuery: (state, { index, newQueryValue }) => { | ||
const newState = [...state]; | ||
newState[index] = newQueryValue; | ||
return newState; | ||
}, | ||
}, | ||
], | ||
}), | ||
selectors: { | ||
hasEmptyQueries: [(selectors) => [selectors.queries], (queries) => queries.indexOf('') >= 0], | ||
hasOnlyOneQuery: [(selectors) => [selectors.queries], (queries) => queries.length <= 1], | ||
}, | ||
}); |
55 changes: 55 additions & 0 deletions
55
...tions/app_search/components/curations/components/curation_queries/curation_query.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import React from 'react'; | ||
|
||
import { shallow } from 'enzyme'; | ||
|
||
import { EuiFieldText } from '@elastic/eui'; | ||
|
||
import { CurationQuery } from './curation_query'; | ||
|
||
describe('CurationQuery', () => { | ||
const props = { | ||
queryValue: 'some query', | ||
onChange: jest.fn(), | ||
onDelete: jest.fn(), | ||
disableDelete: false, | ||
}; | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('renders', () => { | ||
const wrapper = shallow(<CurationQuery {...props} />); | ||
|
||
expect(wrapper.find(EuiFieldText)).toHaveLength(1); | ||
expect(wrapper.find(EuiFieldText).prop('value')).toEqual('some query'); | ||
}); | ||
|
||
it('calls onChange when the input value changes', () => { | ||
const wrapper = shallow(<CurationQuery {...props} />); | ||
wrapper.find(EuiFieldText).simulate('change', { target: { value: 'new query value' } }); | ||
|
||
expect(props.onChange).toHaveBeenCalledWith('new query value'); | ||
}); | ||
|
||
it('calls onDelete when the delete button is clicked', () => { | ||
const wrapper = shallow(<CurationQuery {...props} />); | ||
wrapper.find('[data-test-subj="deleteCurationQueryButton"]').simulate('click'); | ||
|
||
expect(props.onDelete).toHaveBeenCalled(); | ||
}); | ||
|
||
it('disables the delete button if disableDelete is passed', () => { | ||
const wrapper = shallow(<CurationQuery {...props} disableDelete />); | ||
const button = wrapper.find('[data-test-subj="deleteCurationQueryButton"]'); | ||
|
||
expect(button.prop('isDisabled')).toEqual(true); | ||
}); | ||
}); |
Oops, something went wrong.