mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[App Search] Result settings form (#95462)
This commit is contained in:
parent
6ee85e8835
commit
80a19a86d7
21 changed files with 1064 additions and 8 deletions
|
@ -10,6 +10,8 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FieldResultSetting } from './types';
|
||||
|
||||
export const DEFAULT_SNIPPET_SIZE = 100;
|
||||
export const SIZE_FIELD_MINIMUM = 20;
|
||||
export const SIZE_FIELD_MAXIMUM = 1000;
|
||||
|
||||
export const RESULT_SETTINGS_TITLE = i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.title',
|
||||
|
|
|
@ -5,19 +5,33 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import '../../../__mocks__/shallow_useeffect.mock';
|
||||
|
||||
import { setMockActions } from '../../../__mocks__';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { ResultSettings } from './result_settings';
|
||||
import { ResultSettingsTable } from './result_settings_table';
|
||||
|
||||
describe('RelevanceTuning', () => {
|
||||
const actions = {
|
||||
initializeResultSettingsData: jest.fn(),
|
||||
};
|
||||
beforeEach(() => {
|
||||
setMockActions(actions);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<ResultSettings engineBreadcrumb={['test']} />);
|
||||
expect(wrapper.isEmptyRender()).toBe(false);
|
||||
expect(wrapper.find(ResultSettingsTable).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('initializes result settings data when mounted', () => {
|
||||
shallow(<ResultSettings engineBreadcrumb={['test']} />);
|
||||
expect(actions.initializeResultSettingsData).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,29 +5,44 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { EuiPageHeader, EuiPageContentBody, EuiPageContent } from '@elastic/eui';
|
||||
import { useActions } from 'kea';
|
||||
|
||||
import { EuiPageHeader, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
import { FlashMessages } from '../../../shared/flash_messages';
|
||||
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
|
||||
|
||||
import { RESULT_SETTINGS_TITLE } from './constants';
|
||||
import { ResultSettingsTable } from './result_settings_table';
|
||||
|
||||
import { ResultSettingsLogic } from '.';
|
||||
|
||||
interface Props {
|
||||
engineBreadcrumb: string[];
|
||||
}
|
||||
|
||||
export const ResultSettings: React.FC<Props> = ({ engineBreadcrumb }) => {
|
||||
const { initializeResultSettingsData } = useActions(ResultSettingsLogic);
|
||||
|
||||
useEffect(() => {
|
||||
initializeResultSettingsData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SetPageChrome trail={[...engineBreadcrumb, RESULT_SETTINGS_TITLE]} />
|
||||
<EuiPageHeader pageTitle={RESULT_SETTINGS_TITLE} />
|
||||
<EuiPageContent>
|
||||
<EuiPageContentBody>
|
||||
<FlashMessages />
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
<FlashMessages />
|
||||
<EuiFlexGroup alignItems="flexStart">
|
||||
<EuiFlexItem grow={5}>
|
||||
<ResultSettingsTable />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={3}>
|
||||
<div>TODO</div>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { setMockValues } from '../../../../__mocks__';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EuiTableRow } from '@elastic/eui';
|
||||
|
||||
import { DisabledFieldsBody } from './disabled_fields_body';
|
||||
|
||||
describe('DisabledFieldsBody', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues({
|
||||
schemaConflicts: {
|
||||
foo: {
|
||||
text: ['engine1'],
|
||||
number: ['engine2'],
|
||||
},
|
||||
bar: {
|
||||
text: ['engine1'],
|
||||
number: ['engine2'],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a table row for each field', () => {
|
||||
const wrapper = shallow(<DisabledFieldsBody />);
|
||||
const tableRows = wrapper.find(EuiTableRow);
|
||||
|
||||
expect(tableRows.length).toBe(2);
|
||||
expect(tableRows.at(0).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual(
|
||||
'foo'
|
||||
);
|
||||
expect(tableRows.at(1).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual(
|
||||
'bar'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 } from 'kea';
|
||||
|
||||
import { EuiTableBody, EuiTableRow, EuiTableRowCell, EuiText, EuiHealth } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ResultSettingsLogic } from '..';
|
||||
|
||||
export const DisabledFieldsBody: React.FC = () => {
|
||||
const { schemaConflicts } = useValues(ResultSettingsLogic);
|
||||
return (
|
||||
<EuiTableBody>
|
||||
{Object.keys(schemaConflicts).map((fieldName) => (
|
||||
<EuiTableRow key={fieldName}>
|
||||
<EuiTableRowCell colSpan={6}>
|
||||
<EuiText size="s" data-test-subj="ResultSettingFieldName">
|
||||
<code>{fieldName}</code>
|
||||
</EuiText>
|
||||
<EuiHealth color="warning">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.fieldTypeConflictText',
|
||||
{
|
||||
defaultMessage: 'Field-type conflict',
|
||||
}
|
||||
)}
|
||||
</EuiHealth>
|
||||
</EuiTableRowCell>
|
||||
</EuiTableRow>
|
||||
))}
|
||||
</EuiTableBody>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { DisabledFieldsHeader } from './disabled_fields_header';
|
||||
|
||||
describe('DisabledFieldsHeader', () => {
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<DisabledFieldsHeader />);
|
||||
expect(wrapper.isEmptyRender()).toBe(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { EuiTableRow, EuiTableHeaderCell } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const DisabledFieldsHeader: React.FC = () => {
|
||||
return (
|
||||
<EuiTableRow className="resultSettingsTable__subHeader">
|
||||
<EuiTableHeaderCell align="left" colSpan={5}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.disabledFieldsTitle',
|
||||
{ defaultMessage: 'Disabled fields' }
|
||||
)}
|
||||
</EuiTableHeaderCell>
|
||||
</EuiTableRow>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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 { EuiFieldNumber } from '@elastic/eui';
|
||||
|
||||
import { FieldResultSetting } from '../types';
|
||||
|
||||
import { FieldNumber } from './field_number';
|
||||
|
||||
describe('FieldNumber', () => {
|
||||
const fieldSettings = {
|
||||
raw: true,
|
||||
rawSize: 29,
|
||||
snippet: true,
|
||||
snippetFallback: true,
|
||||
snippetSize: 15,
|
||||
};
|
||||
|
||||
const props = {
|
||||
fieldSettings,
|
||||
fieldName: 'foo',
|
||||
fieldEnabledProperty: 'raw' as keyof FieldResultSetting,
|
||||
fieldSizeProperty: 'rawSize' as keyof FieldResultSetting,
|
||||
updateAction: jest.fn(),
|
||||
clearAction: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('is rendered with its value set from [fieldSizeProperty] in fieldSettings', () => {
|
||||
const wrapper = shallow(<FieldNumber {...props} />);
|
||||
expect(wrapper.find(EuiFieldNumber).prop('value')).toEqual(29);
|
||||
});
|
||||
|
||||
it('has no value if [fieldSizeProperty] in fieldSettings has no value', () => {
|
||||
const wrapper = shallow(
|
||||
<FieldNumber
|
||||
{...{
|
||||
...props,
|
||||
fieldSettings: {
|
||||
raw: true,
|
||||
snippet: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find(EuiFieldNumber).prop('value')).toEqual('');
|
||||
});
|
||||
|
||||
it('is disabled if the [fieldEnabledProperty] in fieldSettings is false', () => {
|
||||
const wrapper = shallow(
|
||||
<FieldNumber
|
||||
{...{
|
||||
...props,
|
||||
fieldSettings: {
|
||||
raw: false,
|
||||
snippet: true,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find(EuiFieldNumber).prop('disabled')).toEqual(true);
|
||||
});
|
||||
|
||||
it('will call updateAction when the value is changed', () => {
|
||||
const wrapper = shallow(<FieldNumber {...props} />);
|
||||
wrapper.simulate('change', { target: { value: '21' } });
|
||||
expect(props.updateAction).toHaveBeenCalledWith('foo', 21);
|
||||
});
|
||||
|
||||
it('will call clearAction when the value is changed', () => {
|
||||
const wrapper = shallow(<FieldNumber {...props} />);
|
||||
wrapper.simulate('change', { target: { value: '' } });
|
||||
expect(props.clearAction).toHaveBeenCalledWith('foo');
|
||||
});
|
||||
|
||||
it('will call updateAction on blur', () => {
|
||||
const wrapper = shallow(<FieldNumber {...props} />);
|
||||
wrapper.simulate('blur', { target: { value: '21' } });
|
||||
expect(props.updateAction).toHaveBeenCalledWith('foo', 21);
|
||||
});
|
||||
|
||||
it('will call updateAction on blur using the minimum possible value if the current value is something other than a number', () => {
|
||||
const wrapper = shallow(<FieldNumber {...props} />);
|
||||
wrapper.simulate('blur', { target: { value: '' } });
|
||||
expect(props.updateAction).toHaveBeenCalledWith('foo', 20);
|
||||
});
|
||||
|
||||
it('will call updateAction on blur using the minimum possible value if the value is something lower than the minimum', () => {
|
||||
const wrapper = shallow(<FieldNumber {...props} />);
|
||||
wrapper.simulate('blur', { target: { value: 5 } });
|
||||
expect(props.updateAction).toHaveBeenCalledWith('foo', 20);
|
||||
});
|
||||
|
||||
it('will call updateAction on blur using the maximum possible value if the value is something above than the maximum', () => {
|
||||
const wrapper = shallow(<FieldNumber {...props} />);
|
||||
wrapper.simulate('blur', { target: { value: 2000 } });
|
||||
expect(props.updateAction).toHaveBeenCalledWith('foo', 1000);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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, { ChangeEvent, FocusEvent } from 'react';
|
||||
|
||||
import { EuiFieldNumber } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SIZE_FIELD_MAXIMUM, SIZE_FIELD_MINIMUM } from '../constants';
|
||||
import { FieldResultSetting } from '../types';
|
||||
|
||||
const updateOrClearSizeForField = (
|
||||
fieldName: string,
|
||||
fieldValue: number,
|
||||
updateAction: (fieldName: string, size: number) => void,
|
||||
clearAction: (fieldName: string) => void
|
||||
) => {
|
||||
if (typeof fieldValue === 'number' && !isNaN(fieldValue)) {
|
||||
updateAction(fieldName, fieldValue);
|
||||
} else {
|
||||
clearAction(fieldName);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFieldNumberChange = (
|
||||
fieldName: string,
|
||||
updateAction: (fieldName: string, size: number) => void,
|
||||
clearAction: (fieldName: string) => void
|
||||
) => {
|
||||
return (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const fieldValue = parseInt(e.target.value, 10);
|
||||
updateOrClearSizeForField(fieldName, fieldValue, updateAction, clearAction);
|
||||
};
|
||||
};
|
||||
|
||||
const handleFieldNumberBlur = (
|
||||
fieldName: string,
|
||||
updateAction: (fieldName: string, size: number) => void,
|
||||
clearAction: (fieldName: string) => void
|
||||
) => {
|
||||
return (e: FocusEvent<HTMLInputElement>) => {
|
||||
const value = parseInt(e.target.value, 10);
|
||||
const fieldValue = Math.min(
|
||||
SIZE_FIELD_MAXIMUM,
|
||||
Math.max(SIZE_FIELD_MINIMUM, isNaN(value) ? 0 : value)
|
||||
);
|
||||
updateOrClearSizeForField(fieldName, fieldValue, updateAction, clearAction);
|
||||
};
|
||||
};
|
||||
|
||||
interface Props {
|
||||
fieldSettings: Partial<FieldResultSetting>;
|
||||
fieldName: string;
|
||||
fieldEnabledProperty: keyof FieldResultSetting;
|
||||
fieldSizeProperty: keyof FieldResultSetting;
|
||||
updateAction: (fieldName: string, size: number) => void;
|
||||
clearAction: (fieldName: string) => void;
|
||||
}
|
||||
|
||||
export const FieldNumber: React.FC<Props> = ({
|
||||
fieldSettings,
|
||||
fieldName,
|
||||
fieldEnabledProperty,
|
||||
fieldSizeProperty,
|
||||
updateAction,
|
||||
clearAction,
|
||||
}) => {
|
||||
return (
|
||||
<EuiFieldNumber
|
||||
value={
|
||||
typeof fieldSettings[fieldSizeProperty] === 'number'
|
||||
? (fieldSettings[fieldSizeProperty] as number)
|
||||
: ''
|
||||
}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.numberFieldPlaceholder',
|
||||
{ defaultMessage: 'No limit' }
|
||||
)}
|
||||
disabled={!fieldSettings[fieldEnabledProperty]}
|
||||
min={SIZE_FIELD_MINIMUM}
|
||||
max={SIZE_FIELD_MAXIMUM}
|
||||
onChange={handleFieldNumberChange(fieldName, updateAction, clearAction)}
|
||||
onBlur={handleFieldNumberBlur(fieldName, updateAction, clearAction)}
|
||||
size={4}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { ResultSettingsTable } from './result_settings_table';
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
|
||||
import { EuiTableRow } from '@elastic/eui';
|
||||
|
||||
import { NonTextFieldsBody } from './non_text_fields_body';
|
||||
|
||||
describe('NonTextFieldsBody', () => {
|
||||
const values = {
|
||||
nonTextResultFields: {
|
||||
foo: {
|
||||
raw: false,
|
||||
},
|
||||
zoo: {
|
||||
raw: true,
|
||||
rawSize: 5,
|
||||
},
|
||||
bar: {
|
||||
raw: true,
|
||||
rawSize: 5,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
toggleRawForField: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues(values);
|
||||
setMockActions(actions);
|
||||
});
|
||||
|
||||
const getTableRows = (wrapper: ShallowWrapper) => wrapper.find(EuiTableRow);
|
||||
const getBarTableRow = (wrapper: ShallowWrapper) => getTableRows(wrapper).at(0);
|
||||
|
||||
it('renders a table row for each field, sorted by field name', () => {
|
||||
const wrapper = shallow(<NonTextFieldsBody />);
|
||||
const tableRows = getTableRows(wrapper);
|
||||
|
||||
expect(tableRows.length).toBe(3);
|
||||
expect(tableRows.at(0).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual(
|
||||
'bar'
|
||||
);
|
||||
expect(tableRows.at(1).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual(
|
||||
'foo'
|
||||
);
|
||||
expect(tableRows.at(2).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual(
|
||||
'zoo'
|
||||
);
|
||||
});
|
||||
|
||||
describe('the "raw" checkbox within each table row', () => {
|
||||
const getRawCheckbox = () => {
|
||||
const wrapper = shallow(<NonTextFieldsBody />);
|
||||
const tableRow = getBarTableRow(wrapper);
|
||||
return tableRow.find('[data-test-subj="ResultSettingRawCheckBox"]');
|
||||
};
|
||||
|
||||
it('is rendered with its checked property set from state', () => {
|
||||
const rawCheckbox = getRawCheckbox();
|
||||
expect(rawCheckbox.prop('checked')).toEqual(values.nonTextResultFields.bar.raw);
|
||||
});
|
||||
|
||||
it("calls 'toggleRawForField' when it is clicked by a user", () => {
|
||||
const rawCheckbox = getRawCheckbox();
|
||||
rawCheckbox.simulate('change');
|
||||
expect(actions.toggleRawForField).toHaveBeenCalledWith('bar');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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, { useMemo } from 'react';
|
||||
|
||||
import { useValues, useActions } from 'kea';
|
||||
|
||||
import {
|
||||
EuiTableBody,
|
||||
EuiTableRow,
|
||||
EuiTableRowCell,
|
||||
EuiCheckbox,
|
||||
EuiTableRowCellCheckbox,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { ResultSettingsLogic } from '..';
|
||||
import { FieldResultSetting } from '../types';
|
||||
|
||||
export const NonTextFieldsBody: React.FC = () => {
|
||||
const { nonTextResultFields } = useValues(ResultSettingsLogic);
|
||||
const { toggleRawForField } = useActions(ResultSettingsLogic);
|
||||
|
||||
const resultSettingsArray: Array<[string, Partial<FieldResultSetting>]> = useMemo(() => {
|
||||
return Object.entries(nonTextResultFields).sort(([aFieldName], [bFieldName]) =>
|
||||
aFieldName > bFieldName ? 1 : -1
|
||||
);
|
||||
}, [nonTextResultFields]);
|
||||
|
||||
return (
|
||||
<EuiTableBody>
|
||||
{resultSettingsArray.map(([fieldName, fieldSettings]) => (
|
||||
<EuiTableRow key={fieldName}>
|
||||
<EuiTableRowCell data-test-subj="ResultSettingFieldName">
|
||||
<code>{fieldName}</code>
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCellCheckbox>
|
||||
<EuiCheckbox
|
||||
data-test-subj="ResultSettingRawCheckBox"
|
||||
id={`${fieldName}-raw}`}
|
||||
checked={!!fieldSettings.raw}
|
||||
onChange={() => {
|
||||
toggleRawForField(fieldName);
|
||||
}}
|
||||
/>
|
||||
</EuiTableRowCellCheckbox>
|
||||
<EuiTableRowCell colSpan={4} aria-hidden />
|
||||
</EuiTableRow>
|
||||
))}
|
||||
</EuiTableBody>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { NonTextFieldsHeader } from './non_text_fields_header';
|
||||
|
||||
describe('NonTextFieldsHeader', () => {
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<NonTextFieldsHeader />);
|
||||
expect(wrapper.isEmptyRender()).toBe(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { EuiTableRow, EuiTableHeaderCell } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const NonTextFieldsHeader: React.FC = () => {
|
||||
return (
|
||||
<EuiTableRow className="resultSettingsTable__subHeader">
|
||||
<EuiTableHeaderCell align="left">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.nonTextFieldsTitle',
|
||||
{ defaultMessage: 'Non-text fields' }
|
||||
)}
|
||||
</EuiTableHeaderCell>
|
||||
<EuiTableHeaderCell align="center">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.rawTitle',
|
||||
{ defaultMessage: 'Raw' }
|
||||
)}
|
||||
</EuiTableHeaderCell>
|
||||
<EuiTableHeaderCell colSpan={4} aria-hidden />
|
||||
</EuiTableRow>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
.resultSettingsTable {
|
||||
table-layout: auto;
|
||||
|
||||
&__columnLabels {
|
||||
.euiTableHeaderCell {
|
||||
border-bottom: none;
|
||||
padding-bottom: $euiSizeM;
|
||||
}
|
||||
}
|
||||
|
||||
.euiTableRow:last-of-type {
|
||||
.euiTableRowCell, .euiTableRowCellCheckbox {
|
||||
// Because we are using the large border-top as a way of spacing, and table borders
|
||||
// are collapsed, these tables do not have a bottom border. The exception to that is
|
||||
// if the tbody is the last tbody of the table. To make it consistent, we explicitly
|
||||
// disable all bottom borders here
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
tbody + &__subHeader {
|
||||
// Since this table has multiple sets of thead + tbody's, this is our way of keeping
|
||||
// vertical space between the two
|
||||
border-top: solid $euiSizeL * 2 transparent;
|
||||
}
|
||||
|
||||
.euiTableRowCellCheckbox .euiTableCellContent {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 { setMockValues } from '../../../../__mocks__/kea.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { DisabledFieldsHeader } from './disabled_fields_header';
|
||||
import { NonTextFieldsBody } from './non_text_fields_body';
|
||||
import { ResultSettingsTable } from './result_settings_table';
|
||||
import { TextFieldsBody } from './text_fields_body';
|
||||
|
||||
describe('ResultSettingsTable', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues({
|
||||
textResultFields: { foo: { raw: true, rawSize: 5, snippet: false, snippetFallback: false } },
|
||||
nonTextResultFields: {
|
||||
bar: { raw: true, rawSize: 5, snippet: false, snippetFallback: false },
|
||||
},
|
||||
schemaConflicts: {
|
||||
foo: {
|
||||
text: ['foo'],
|
||||
number: ['foo'],
|
||||
geolocation: [],
|
||||
date: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<ResultSettingsTable />);
|
||||
expect(wrapper.find(TextFieldsBody).exists()).toBe(true);
|
||||
expect(wrapper.find(NonTextFieldsBody).exists()).toBe(true);
|
||||
expect(wrapper.find(DisabledFieldsHeader).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('will hide sections that have no data available to show', () => {
|
||||
setMockValues({
|
||||
textResultFields: {},
|
||||
nonTextResultFields: {},
|
||||
schemaConflicts: {},
|
||||
});
|
||||
|
||||
const wrapper = shallow(<ResultSettingsTable />);
|
||||
expect(wrapper.find(TextFieldsBody).exists()).toBe(false);
|
||||
expect(wrapper.find(NonTextFieldsBody).exists()).toBe(false);
|
||||
expect(wrapper.find(DisabledFieldsHeader).exists()).toBe(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 } from 'kea';
|
||||
|
||||
import { EuiTable } from '@elastic/eui';
|
||||
|
||||
import { ResultSettingsLogic } from '..';
|
||||
|
||||
import { DisabledFieldsBody } from './disabled_fields_body';
|
||||
import { DisabledFieldsHeader } from './disabled_fields_header';
|
||||
import { NonTextFieldsBody } from './non_text_fields_body';
|
||||
import { NonTextFieldsHeader } from './non_text_fields_header';
|
||||
import { TextFieldsBody } from './text_fields_body';
|
||||
import { TextFieldsHeader } from './text_fields_header';
|
||||
|
||||
import './result_settings_table.scss';
|
||||
|
||||
export const ResultSettingsTable: React.FC = () => {
|
||||
const { schemaConflicts, textResultFields, nonTextResultFields } = useValues(ResultSettingsLogic);
|
||||
|
||||
// TODO This table currently has mutiple theads, which is invalid html. We could change these subheaders to be EuiTableRow instead of EuiTableHeader
|
||||
// to alleviate the issue.
|
||||
return (
|
||||
<EuiTable className="resultSettingsTable" responsive={false}>
|
||||
{!!Object.keys(textResultFields).length && (
|
||||
<>
|
||||
<TextFieldsHeader />
|
||||
<TextFieldsBody />
|
||||
</>
|
||||
)}
|
||||
{!!Object.keys(nonTextResultFields).length && (
|
||||
<>
|
||||
<NonTextFieldsHeader />
|
||||
<NonTextFieldsBody />
|
||||
</>
|
||||
)}
|
||||
{!!Object.keys(schemaConflicts).length && (
|
||||
<>
|
||||
<DisabledFieldsHeader />
|
||||
<DisabledFieldsBody />
|
||||
</>
|
||||
)}
|
||||
</EuiTable>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
|
||||
import { EuiTableRow } from '@elastic/eui';
|
||||
|
||||
import { TextFieldsBody } from './text_fields_body';
|
||||
|
||||
describe('TextFieldsBody', () => {
|
||||
const values = {
|
||||
textResultFields: {
|
||||
foo: {
|
||||
raw: false,
|
||||
snippet: true,
|
||||
snippetFallback: true,
|
||||
snippetSize: 15,
|
||||
},
|
||||
zoo: {
|
||||
raw: true,
|
||||
rawSize: 5,
|
||||
snippet: false,
|
||||
snippetFallback: false,
|
||||
},
|
||||
bar: {
|
||||
raw: true,
|
||||
rawSize: 5,
|
||||
snippet: false,
|
||||
snippetFallback: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
toggleRawForField: jest.fn(),
|
||||
updateRawSizeForField: jest.fn(),
|
||||
clearRawSizeForField: jest.fn(),
|
||||
toggleSnippetForField: jest.fn(),
|
||||
updateSnippetSizeForField: jest.fn(),
|
||||
clearSnippetSizeForField: jest.fn(),
|
||||
toggleSnippetFallbackForField: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues(values);
|
||||
setMockActions(actions);
|
||||
});
|
||||
|
||||
const getTableRows = (wrapper: ShallowWrapper) => wrapper.find(EuiTableRow);
|
||||
const getBarTableRow = (wrapper: ShallowWrapper) => getTableRows(wrapper).at(0);
|
||||
const getFooTableRow = (wrapper: ShallowWrapper) => getTableRows(wrapper).at(1);
|
||||
|
||||
it('renders a table row for each field, sorted by field name', () => {
|
||||
const wrapper = shallow(<TextFieldsBody />);
|
||||
const tableRows = getTableRows(wrapper);
|
||||
|
||||
expect(tableRows.length).toBe(3);
|
||||
expect(tableRows.at(0).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual(
|
||||
'bar'
|
||||
);
|
||||
expect(tableRows.at(1).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual(
|
||||
'foo'
|
||||
);
|
||||
expect(tableRows.at(2).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual(
|
||||
'zoo'
|
||||
);
|
||||
});
|
||||
|
||||
describe('the "raw" checkbox within each table row', () => {
|
||||
const getRawCheckbox = () => {
|
||||
const wrapper = shallow(<TextFieldsBody />);
|
||||
const tableRow = getBarTableRow(wrapper);
|
||||
return tableRow.find('[data-test-subj="ResultSettingRawCheckBox"]');
|
||||
};
|
||||
|
||||
it('is rendered with its checked property set from state', () => {
|
||||
const rawCheckbox = getRawCheckbox();
|
||||
expect(rawCheckbox.prop('checked')).toEqual(values.textResultFields.bar.raw);
|
||||
});
|
||||
|
||||
it("calls 'toggleRawForField' when it is clicked by a user", () => {
|
||||
const rawCheckbox = getRawCheckbox();
|
||||
rawCheckbox.simulate('change');
|
||||
expect(actions.toggleRawForField).toHaveBeenCalledWith('bar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('the "snippet" checkbox within each table row', () => {
|
||||
const getSnippetCheckbox = () => {
|
||||
const wrapper = shallow(<TextFieldsBody />);
|
||||
const tableRow = getFooTableRow(wrapper);
|
||||
return tableRow.find('[data-test-subj="ResultSettingSnippetTextBox"]');
|
||||
};
|
||||
|
||||
it('is rendered with its checked property set from state', () => {
|
||||
const snippetCheckbox = getSnippetCheckbox();
|
||||
expect(snippetCheckbox.prop('checked')).toEqual(values.textResultFields.foo.snippet);
|
||||
});
|
||||
|
||||
it("calls 'toggleRawForField' when it is clicked by a user", () => {
|
||||
const snippetCheckbox = getSnippetCheckbox();
|
||||
snippetCheckbox.simulate('change');
|
||||
expect(actions.toggleSnippetForField).toHaveBeenCalledWith('foo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('the "fallback" checkbox within each table row', () => {
|
||||
const getFallbackCheckbox = () => {
|
||||
const wrapper = shallow(<TextFieldsBody />);
|
||||
const tableRow = getFooTableRow(wrapper);
|
||||
return tableRow.find('[data-test-subj="ResultSettingFallbackTextBox"]');
|
||||
};
|
||||
|
||||
it('is rendered with its checked property set from state', () => {
|
||||
const fallbackCheckbox = getFallbackCheckbox();
|
||||
expect(fallbackCheckbox.prop('checked')).toEqual(values.textResultFields.foo.snippetFallback);
|
||||
});
|
||||
|
||||
it('is disabled if snippets are disabled for this field', () => {
|
||||
const wrapper = shallow(<TextFieldsBody />);
|
||||
const tableRow = getBarTableRow(wrapper);
|
||||
const fallbackCheckbox = tableRow.find('[data-test-subj="ResultSettingFallbackTextBox"]');
|
||||
expect(fallbackCheckbox.prop('disabled')).toEqual(true);
|
||||
});
|
||||
|
||||
it("calls 'toggleSnippetFallbackForField' when it is clicked by a user", () => {
|
||||
const fallbackCheckbox = getFallbackCheckbox();
|
||||
fallbackCheckbox.simulate('change');
|
||||
expect(actions.toggleSnippetFallbackForField).toHaveBeenCalledWith('foo');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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, { useMemo } from 'react';
|
||||
|
||||
import { useValues, useActions } from 'kea';
|
||||
|
||||
import {
|
||||
EuiTableBody,
|
||||
EuiTableRow,
|
||||
EuiTableRowCell,
|
||||
EuiTableRowCellCheckbox,
|
||||
EuiCheckbox,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { ResultSettingsLogic } from '../result_settings_logic';
|
||||
import { FieldResultSetting } from '../types';
|
||||
|
||||
import { FieldNumber } from './field_number';
|
||||
|
||||
export const TextFieldsBody: React.FC = () => {
|
||||
const { textResultFields } = useValues(ResultSettingsLogic);
|
||||
const {
|
||||
toggleRawForField,
|
||||
updateRawSizeForField,
|
||||
clearRawSizeForField,
|
||||
toggleSnippetForField,
|
||||
updateSnippetSizeForField,
|
||||
clearSnippetSizeForField,
|
||||
toggleSnippetFallbackForField,
|
||||
} = useActions(ResultSettingsLogic);
|
||||
|
||||
const resultSettingsArray: Array<[string, Partial<FieldResultSetting>]> = useMemo(() => {
|
||||
return Object.entries(textResultFields).sort(([aFieldName], [bFieldName]) =>
|
||||
aFieldName > bFieldName ? 1 : -1
|
||||
);
|
||||
}, [textResultFields]);
|
||||
|
||||
return (
|
||||
<EuiTableBody>
|
||||
{resultSettingsArray.map(([fieldName, fieldSettings]) => (
|
||||
<EuiTableRow key={fieldName}>
|
||||
<EuiTableRowCell data-test-subj="ResultSettingFieldName">
|
||||
<code>{fieldName}</code>
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCellCheckbox>
|
||||
<EuiCheckbox
|
||||
data-test-subj="ResultSettingRawCheckBox"
|
||||
id={`${fieldName}-raw}`}
|
||||
checked={!!fieldSettings.raw}
|
||||
onChange={() => {
|
||||
toggleRawForField(fieldName);
|
||||
}}
|
||||
/>
|
||||
</EuiTableRowCellCheckbox>
|
||||
<EuiTableRowCell align="center">
|
||||
<FieldNumber
|
||||
fieldName={fieldName}
|
||||
fieldEnabledProperty="raw"
|
||||
fieldSizeProperty="rawSize"
|
||||
fieldSettings={fieldSettings}
|
||||
updateAction={updateRawSizeForField}
|
||||
clearAction={clearRawSizeForField}
|
||||
/>
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCellCheckbox>
|
||||
<EuiCheckbox
|
||||
data-test-subj="ResultSettingSnippetTextBox"
|
||||
id={`${fieldName}-snippet}`}
|
||||
checked={!!fieldSettings.snippet}
|
||||
onChange={() => {
|
||||
toggleSnippetForField(fieldName);
|
||||
}}
|
||||
/>
|
||||
</EuiTableRowCellCheckbox>
|
||||
<EuiTableRowCellCheckbox>
|
||||
<EuiCheckbox
|
||||
data-test-subj="ResultSettingFallbackTextBox"
|
||||
id={`${fieldName}-snippetFallback}`}
|
||||
checked={fieldSettings.snippetFallback}
|
||||
disabled={!fieldSettings.snippet}
|
||||
onChange={() => {
|
||||
toggleSnippetFallbackForField(fieldName);
|
||||
}}
|
||||
/>
|
||||
</EuiTableRowCellCheckbox>
|
||||
<EuiTableRowCell align="center">
|
||||
<FieldNumber
|
||||
fieldName={fieldName}
|
||||
fieldEnabledProperty="snippet"
|
||||
fieldSizeProperty="snippetSize"
|
||||
fieldSettings={fieldSettings}
|
||||
updateAction={updateSnippetSizeForField}
|
||||
clearAction={clearSnippetSizeForField}
|
||||
/>
|
||||
</EuiTableRowCell>
|
||||
</EuiTableRow>
|
||||
))}
|
||||
</EuiTableBody>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { TextFieldsHeader } from './text_fields_header';
|
||||
|
||||
describe('TextFieldsHeader', () => {
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<TextFieldsHeader />);
|
||||
expect(wrapper.isEmptyRender()).toBe(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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 { EuiTableRow, EuiTableHeader, EuiTableHeaderCell, EuiIconTip } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const TextFieldsHeader: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<EuiTableHeader className="resultSettingsTable__columnLabels">
|
||||
<EuiTableHeaderCell align="left" />
|
||||
<EuiTableHeaderCell align="center" colSpan={2}>
|
||||
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawTitle', {
|
||||
defaultMessage: 'Raw',
|
||||
})}
|
||||
<EuiIconTip
|
||||
position="top"
|
||||
content={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawTooltip',
|
||||
{
|
||||
defaultMessage:
|
||||
'A raw field is an exact representation of a field value. Must be at least 20 characters. Defaults to the entire field.',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiTableHeaderCell>
|
||||
<EuiTableHeaderCell align="center" colSpan={3}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.highlightingTitle',
|
||||
{
|
||||
defaultMessage: 'Highlighting',
|
||||
}
|
||||
)}
|
||||
<EuiIconTip
|
||||
position="top"
|
||||
content={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.highlightingTooltip',
|
||||
{
|
||||
defaultMessage:
|
||||
'A snippet is an escaped representaiton of a field value. Query matches are encapsulated in <em> tags for highlighting. Fallback will look for a snippet match, but fallback to an escaped raw value if none is found. Range is between 20-1000. Defaults to 100.',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiTableHeaderCell>
|
||||
</EuiTableHeader>
|
||||
<EuiTableRow className="resultSettingsTable__subHeader">
|
||||
<EuiTableHeaderCell align="left">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.textFieldsTitle',
|
||||
{ defaultMessage: 'Text fields' }
|
||||
)}
|
||||
</EuiTableHeaderCell>
|
||||
{/* TODO Right now the stacked "Raw" ths leads screen readers to reading out Raw - Raw - Raw 3x in a row once you get down to the non-text fields. We should consider either:
|
||||
Channging this "Raw" column to something like "Enabled"
|
||||
Or losing the RAW vs HIGHLIGHTING top-level headings */}
|
||||
<EuiTableHeaderCell align="center">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.rawTitle',
|
||||
{ defaultMessage: 'Raw' }
|
||||
)}
|
||||
</EuiTableHeaderCell>
|
||||
<EuiTableHeaderCell align="center">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.maxSizeTitle',
|
||||
{ defaultMessage: 'Max size' }
|
||||
)}
|
||||
</EuiTableHeaderCell>
|
||||
<EuiTableHeaderCell align="center">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.snippetTitle',
|
||||
{ defaultMessage: 'Snippet' }
|
||||
)}
|
||||
</EuiTableHeaderCell>
|
||||
<EuiTableHeaderCell align="center">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.fallbackTitle',
|
||||
{ defaultMessage: 'Fallback' }
|
||||
)}
|
||||
</EuiTableHeaderCell>
|
||||
<EuiTableHeaderCell align="center">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.maxSizeTitle',
|
||||
{ defaultMessage: 'Max size' }
|
||||
)}
|
||||
</EuiTableHeaderCell>
|
||||
</EuiTableRow>
|
||||
</>
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue