@@ -101,7 +105,36 @@ export const SearchExperience: React.FC = () => {
view={SortingView}
/>
-
+ {fields.filterFields.length > 0 ? (
+ <>
+ {fields.filterFields.map((fieldName) => (
+
+ ))}
+
+ {i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.documents.search.customizationButton',
+ {
+ defaultMessage: 'Customize filters and sort',
+ }
+ )}
+
+ >
+ ) : (
+
+ )}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/types.ts
new file mode 100644
index 000000000000..0cde0f94b773
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/types.ts
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+export interface Fields {
+ filterFields: string[];
+ sortFields: string[];
+}
+
+export type SortDirection = 'asc' | 'desc';
+
+export interface SortOption {
+ name: string;
+ value: string;
+ direction: SortDirection;
+}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/index.ts
index 8c88fc81d3a3..7032fa1a9a06 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/index.ts
@@ -9,3 +9,4 @@ export { SortingView } from './sorting_view';
export { ResultView } from './result_view';
export { ResultsPerPageView } from './results_per_page_view';
export { PagingView } from './paging_view';
+export { MultiCheckboxFacetsView } from './multi_checkbox_facets_view';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.test.tsx
new file mode 100644
index 000000000000..7f43ca12652c
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.test.tsx
@@ -0,0 +1,99 @@
+/*
+ * 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 React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { MultiCheckboxFacetsView } from './multi_checkbox_facets_view';
+
+describe('MultiCheckboxFacetsView', () => {
+ const props = {
+ label: 'foo',
+ options: [
+ {
+ value: 'value1',
+ selected: false,
+ },
+ {
+ value: 'value2',
+ selected: false,
+ },
+ ],
+ showMore: true,
+ onMoreClick: jest.fn(),
+ onRemove: jest.fn(),
+ onSelect: jest.fn(),
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders', () => {
+ const wrapper = shallow();
+ expect(wrapper.isEmptyRender()).toBe(false);
+ });
+
+ it('calls onMoreClick when more button is clicked', () => {
+ const wrapper = shallow();
+ wrapper.find('[data-test-subj="more"]').simulate('click');
+ expect(props.onMoreClick).toHaveBeenCalled();
+ });
+
+ it('calls onSelect when an option is selected', () => {
+ const wrapper = shallow();
+ wrapper.find('[data-test-subj="checkbox-group"]').simulate('change', 'generated-id_1');
+ expect(props.onSelect).toHaveBeenCalledWith('value2');
+ });
+
+ it('calls onRemove if the option was already selected', () => {
+ const wrapper = shallow(
+
+ );
+ wrapper.find('[data-test-subj="checkbox-group"]').simulate('change', 'generated-id_1');
+ expect(props.onRemove).toHaveBeenCalledWith('value2');
+ });
+
+ it('it passes options to EuiCheckboxGroup, converting no values to the text "No Value"', () => {
+ const wrapper = shallow(
+
+ );
+ const options = wrapper.find('[data-test-subj="checkbox-group"]').prop('options');
+ expect(options).toEqual([
+ { id: 'generated-id_0', label: 'value1' },
+ { id: 'generated-id_1', label: '' },
+ ]);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx
new file mode 100644
index 000000000000..df61e6e3dcc0
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx
@@ -0,0 +1,114 @@
+/*
+ * 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 React from 'react';
+
+import {
+ htmlIdGenerator,
+ EuiCheckboxGroup,
+ EuiFlexGroup,
+ EuiButtonEmpty,
+ EuiSpacer,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+interface Option {
+ value: string;
+ selected: boolean;
+}
+
+interface Props {
+ label: string;
+ options: Option[];
+ showMore: boolean;
+ onMoreClick(): void;
+ onRemove(id: string): void;
+ onSelect(id: string): void;
+}
+
+const getIndexFromId = (id: string) => parseInt(id.split('_')[1], 10);
+
+export const MultiCheckboxFacetsView: React.FC = ({
+ label,
+ onMoreClick,
+ onRemove,
+ onSelect,
+ options,
+ showMore,
+}) => {
+ const getId = htmlIdGenerator();
+
+ const optionToCheckBoxGroupOption = (option: Option, index: number) => ({
+ id: getId(String(index)),
+ label:
+ option.value ||
+ i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.documents.search.multiCheckboxFacetsView.noValue.selectOption',
+ {
+ defaultMessage: '',
+ }
+ ),
+ });
+
+ const optionToSelectedMapReducer = (
+ selectedMap: { [name: string]: boolean },
+ option: Option,
+ index: number
+ ) => {
+ if (option.selected) {
+ selectedMap[getId(String(index))] = true;
+ }
+ return selectedMap;
+ };
+
+ const checkboxGroupOptions = options.map(optionToCheckBoxGroupOption);
+ const idToSelectedMap = options.reduce(optionToSelectedMapReducer, {});
+
+ const onChange = (checkboxId: string) => {
+ const index = getIndexFromId(checkboxId);
+ const option = options[index];
+ if (option.selected) {
+ onRemove(option.value);
+ return;
+ }
+ onSelect(option.value);
+ };
+
+ return (
+ <>
+
+ {showMore && (
+ <>
+
+
+
+ {i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.documents.search.multiCheckboxFacetsView.showMore',
+ {
+ defaultMessage: 'Show more',
+ }
+ )}
+
+
+ >
+ )}
+ >
+ );
+};
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 07befe8a26b2..1bbf4b803375 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -7208,8 +7208,6 @@
"xpack.enterpriseSearch.appSearch.documents.search.indexingGuide": "インデックスガイドをお読みください",
"xpack.enterpriseSearch.appSearch.documents.search.noResults": "「{resultSearchTerm}」の結果がありません。",
"xpack.enterpriseSearch.appSearch.documents.search.placeholder": "ドキュメントのフィルター...",
- "xpack.enterpriseSearch.appSearch.documents.search.recentlyUploadedAsc": "最近アップロードされたドキュメント(昇順)",
- "xpack.enterpriseSearch.appSearch.documents.search.recentlyUploadedDesc": "最近アップロードされたドキュメント(降順)",
"xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.ariaLabel": "1 ページに表示する結果数",
"xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.show": "表示:",
"xpack.enterpriseSearch.appSearch.documents.search.sortBy": "並べ替え基準",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 87af04f7dec8..51205a3420be 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -7227,8 +7227,6 @@
"xpack.enterpriseSearch.appSearch.documents.search.indexingGuide": "请阅读索引指南",
"xpack.enterpriseSearch.appSearch.documents.search.noResults": "还没有匹配“{resultSearchTerm}”的结果!",
"xpack.enterpriseSearch.appSearch.documents.search.placeholder": "筛选文档......",
- "xpack.enterpriseSearch.appSearch.documents.search.recentlyUploadedAsc": "最近上传(升序)",
- "xpack.enterpriseSearch.appSearch.documents.search.recentlyUploadedDesc": "最近上传(降序)",
"xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.ariaLabel": "每页要显示的结果数",
"xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.show": "显示:",
"xpack.enterpriseSearch.appSearch.documents.search.sortBy": "排序依据",