Skip to content

Commit

Permalink
[App Search] Wired up configurable Sort and Facets in Documents View (e…
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonStoltz authored Jan 25, 2021
1 parent 000cf36 commit 5462f35
Show file tree
Hide file tree
Showing 14 changed files with 523 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,49 @@ describe('buildSearchUIConfig', () => {
foo: 'text' as SchemaTypes,
bar: 'number' as SchemaTypes,
};
const fields = {
filterFields: ['fieldA', 'fieldB'],
sortFields: [],
};

const config = buildSearchUIConfig(connector, schema);
expect(config.apiConnector).toEqual(connector);
expect(config.searchQuery.result_fields).toEqual({
bar: {
raw: {},
snippet: {
fallback: true,
size: 300,
},
const config = buildSearchUIConfig(connector, schema, fields);
expect(config).toEqual({
alwaysSearchOnInitialLoad: true,
apiConnector: connector,
initialState: {
sortDirection: 'desc',
sortField: 'id',
},
foo: {
raw: {},
snippet: {
fallback: true,
size: 300,
searchQuery: {
disjunctiveFacets: ['fieldA', 'fieldB'],
facets: {
fieldA: {
size: 30,
type: 'value',
},
fieldB: {
size: 30,
type: 'value',
},
},
result_fields: {
bar: {
raw: {},
snippet: {
fallback: true,
size: 300,
},
},
foo: {
raw: {},
snippet: {
fallback: true,
size: 300,
},
},
},
},
trackUrlState: false,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@
*/

import { Schema } from '../../../../shared/types';
import { Fields } from './types';

export const buildSearchUIConfig = (apiConnector: object, schema: Schema, fields: Fields) => {
const facets = fields.filterFields.reduce(
(facetsConfig, fieldName) => ({
...facetsConfig,
[fieldName]: { type: 'value', size: 30 },
}),
{}
);

export const buildSearchUIConfig = (apiConnector: object, schema: Schema) => {
return {
alwaysSearchOnInitialLoad: true,
apiConnector,
Expand All @@ -16,6 +25,8 @@ export const buildSearchUIConfig = (apiConnector: object, schema: Schema) => {
sortField: 'id',
},
searchQuery: {
disjunctiveFacets: fields.filterFields,
facets,
result_fields: Object.keys(schema).reduce((acc: { [key: string]: object }, key: string) => {
acc[key] = {
snippet: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { buildSortOptions } from './build_sort_options';

describe('buildSortOptions', () => {
it('builds sort options from a list of field names', () => {
const sortOptions = buildSortOptions(
{
filterFields: [],
sortFields: ['fieldA', 'fieldB'],
},
[
{
name: 'Relevance (asc)',
value: 'id',
direction: 'desc',
},
{
name: 'Relevance (desc)',
value: 'id',
direction: 'asc',
},
]
);

expect(sortOptions).toEqual([
{
name: 'Relevance (asc)',
value: 'id',
direction: 'desc',
},
{
name: 'Relevance (desc)',
value: 'id',
direction: 'asc',
},
{
direction: 'asc',
name: 'fieldA (asc)',
value: 'fieldA',
},
{
direction: 'desc',
name: 'fieldA (desc)',
value: 'fieldA',
},
{
direction: 'asc',
name: 'fieldB (asc)',
value: 'fieldB',
},
{
direction: 'desc',
name: 'fieldB (desc)',
value: 'fieldB',
},
]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { flatten } from 'lodash';

import { Fields, SortOption, SortDirection } from './types';
import { ASCENDING, DESCENDING } from './constants';

const fieldNameToSortOptions = (fieldName: string): SortOption[] =>
['asc', 'desc'].map((direction) => ({
name: direction === 'asc' ? ASCENDING(fieldName) : DESCENDING(fieldName),
value: fieldName,
direction: direction as SortDirection,
}));

/**
* Adds two sort options for a given field, a "desc" and an "asc" option.
*/
export const buildSortOptions = (
fields: Fields,
defaultSortOptions: SortOption[]
): SortOption[] => {
const sortFieldsOptions = flatten(fields.sortFields.map(fieldNameToSortOptions));
const sortingOptions = [...defaultSortOptions, ...sortFieldsOptions];
return sortingOptions;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { i18n } from '@kbn/i18n';

export const ASCENDING = (fieldName: string) =>
i18n.translate(
'xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.ascendingDropDownOptionLabel',
{
defaultMessage: '{fieldName} (asc)',
values: { fieldName },
}
);

export const DESCENDING = (fieldName: string) =>
i18n.translate(
'xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.descendingDropDownOptionLabel',
{
defaultMessage: '{fieldName} (desc)',
values: { fieldName },
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,12 @@
background-color: $euiPageBackgroundColor;
padding: $euiSizeL;
}

.documentsSearchExperience__facet {
line-height: 0;

.euiCheckbox__label {
@include euiTextTruncate;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,19 @@
import '../../../../__mocks__/enterprise_search_url.mock';
import { setMockValues } from '../../../../__mocks__';

const mockSetFields = jest.fn();

jest.mock('../../../../shared/use_local_storage', () => ({
useLocalStorage: jest.fn(() => [
{
filterFields: ['a', 'b', 'c'],
sortFields: ['d', 'c'],
},
mockSetFields,
]),
useLocalStorage: jest.fn(),
}));
import { useLocalStorage } from '../../../../shared/use_local_storage';

import React from 'react';
// @ts-expect-error types are not available for this package yet
import { SearchProvider } from '@elastic/react-search-ui';
import { shallow } from 'enzyme';
import { SearchProvider, Facet } from '@elastic/react-search-ui';
import { shallow, ShallowWrapper } from 'enzyme';

import { CustomizationCallout } from './customization_callout';
import { CustomizationModal } from './customization_modal';
import { Fields } from './types';

import { SearchExperience } from './search_experience';

Expand All @@ -36,8 +30,16 @@ describe('SearchExperience', () => {
apiKey: '1234',
},
};
const mockSetFields = jest.fn();
const setFieldsInLocalStorage = (fields: Fields) => {
(useLocalStorage as jest.Mock).mockImplementation(() => [fields, mockSetFields]);
};

beforeEach(() => {
setFieldsInLocalStorage({
filterFields: ['a', 'b', 'c'],
sortFields: ['d', 'c'],
});
jest.clearAllMocks();
setMockValues(values);
});
Expand All @@ -47,12 +49,60 @@ describe('SearchExperience', () => {
expect(wrapper.find(SearchProvider).length).toBe(1);
});

describe('when there are no selected filter fields', () => {
let wrapper: ShallowWrapper;
beforeEach(() => {
setFieldsInLocalStorage({
filterFields: [],
sortFields: ['a', 'b'],
});
wrapper = shallow(<SearchExperience />);
});

it('shows a customize callout instead of a button if no fields are yet selected', () => {
expect(wrapper.find(CustomizationCallout).exists()).toBe(true);
expect(wrapper.find('[data-test-subj="customize"]').exists()).toBe(false);
});

it('will show the customization modal when clicked', () => {
expect(wrapper.find(CustomizationModal).exists()).toBe(false);
wrapper.find(CustomizationCallout).simulate('click');

expect(wrapper.find(CustomizationModal).exists()).toBe(true);
});
});

describe('when there are selected filter fields', () => {
let wrapper: ShallowWrapper;
beforeEach(() => {
setFieldsInLocalStorage({
filterFields: ['a', 'b'],
sortFields: ['a', 'b'],
});
wrapper = shallow(<SearchExperience />);
});

it('shows a customize button', () => {
expect(wrapper.find(CustomizationCallout).exists()).toBe(false);
expect(wrapper.find('[data-test-subj="customize"]').exists()).toBe(true);
});
});

it('renders Facet components for filter fields', () => {
setFieldsInLocalStorage({
filterFields: ['a', 'b', 'c'],
sortFields: [],
});
const wrapper = shallow(<SearchExperience />);
expect(wrapper.find(Facet).length).toBe(3);
});

describe('customization modal', () => {
it('has a customization modal which can be opened and closed', () => {
const wrapper = shallow(<SearchExperience />);
expect(wrapper.find(CustomizationModal).exists()).toBe(false);

wrapper.find(CustomizationCallout).simulate('click');
wrapper.find('[data-test-subj="customize"]').simulate('click');
expect(wrapper.find(CustomizationModal).exists()).toBe(true);

wrapper.find(CustomizationModal).prop('onClose')();
Expand All @@ -61,14 +111,14 @@ describe('SearchExperience', () => {

it('passes values from localStorage to the customization modal', () => {
const wrapper = shallow(<SearchExperience />);
wrapper.find(CustomizationCallout).simulate('click');
wrapper.find('[data-test-subj="customize"]').simulate('click');
expect(wrapper.find(CustomizationModal).prop('filterFields')).toEqual(['a', 'b', 'c']);
expect(wrapper.find(CustomizationModal).prop('sortFields')).toEqual(['d', 'c']);
});

it('updates selected fields in localStorage and closes modal on save', () => {
const wrapper = shallow(<SearchExperience />);
wrapper.find(CustomizationCallout).simulate('click');
wrapper.find('[data-test-subj="customize"]').simulate('click');
wrapper.find(CustomizationModal).prop('onSave')({
filterFields: ['new', 'filters'],
sortFields: ['new', 'sorts'],
Expand Down
Loading

0 comments on commit 5462f35

Please sign in to comment.