[Actions] Hiding time field selector if no field with date mapping in index in Index Connector flyout (#96080)

* Hiding time field selector if no field with date mapping in index

* Fixing types check

* Updating tooltip

* PR fixes
This commit is contained in:
ymao1 2021-04-07 10:44:12 -04:00 committed by GitHub
parent f945f3a425
commit bb109b533c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 304 additions and 107 deletions

View file

@ -10,6 +10,7 @@ import { mountWithIntl, nextTick } from '@kbn/test/jest';
import { act } from 'react-dom/test-utils';
import { EsIndexActionConnector } from '../types';
import IndexActionConnectorFields from './es_index_connector';
import { EuiComboBox, EuiSwitch, EuiSwitchEvent, EuiSelect } from '@elastic/eui';
jest.mock('../../../../common/lib/kibana');
jest.mock('../../../../common/index_controls', () => ({
@ -19,83 +20,263 @@ jest.mock('../../../../common/index_controls', () => ({
getIndexPatterns: jest.fn(),
}));
const { getIndexPatterns } = jest.requireMock('../../../../common/index_controls');
getIndexPatterns.mockResolvedValueOnce([
{
id: 'indexPattern1',
attributes: {
title: 'indexPattern1',
},
},
{
id: 'indexPattern2',
attributes: {
title: 'indexPattern2',
},
},
]);
const { getFields } = jest.requireMock('../../../../common/index_controls');
async function setup(props: any) {
const wrapper = mountWithIntl(<IndexActionConnectorFields {...props} />);
await act(async () => {
await nextTick();
wrapper.update();
});
return wrapper;
}
function setupGetFieldsResponse(getFieldsWithDateMapping: boolean) {
getFields.mockResolvedValueOnce([
{
type: getFieldsWithDateMapping ? 'date' : 'keyword',
name: 'test1',
},
{
type: 'text',
name: 'test2',
},
]);
}
describe('IndexActionConnectorFields renders', () => {
test('all connector fields is rendered', async () => {
const { getIndexPatterns } = jest.requireMock('../../../../common/index_controls');
getIndexPatterns.mockResolvedValueOnce([
{
id: 'indexPattern1',
attributes: {
title: 'indexPattern1',
},
},
{
id: 'indexPattern2',
attributes: {
title: 'indexPattern2',
},
},
]);
const { getFields } = jest.requireMock('../../../../common/index_controls');
getFields.mockResolvedValueOnce([
{
type: 'date',
name: 'test1',
},
{
type: 'text',
name: 'test2',
},
]);
test('renders correctly when creating connector', async () => {
const props = {
action: {
actionTypeId: '.index',
config: {},
secrets: {},
} as EsIndexActionConnector,
editActionConfig: () => {},
editActionSecrets: () => {},
errors: { index: [] },
readOnly: false,
};
const wrapper = mountWithIntl(<IndexActionConnectorFields {...props} />);
const actionConnector = {
secrets: {},
id: 'test',
actionTypeId: '.index',
name: 'es_index',
config: {
index: 'test',
refresh: false,
executionTimeField: 'test1',
},
} as EsIndexActionConnector;
const wrapper = mountWithIntl(
<IndexActionConnectorFields
action={actionConnector}
errors={{ index: [] }}
editActionConfig={() => {}}
editActionSecrets={() => {}}
readOnly={false}
/>
);
expect(wrapper.find('[data-test-subj="connectorIndexesComboBox"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="indexRefreshCheckbox"]').exists()).toBeTruthy();
// time field switch shouldn't show up initially
expect(wrapper.find('[data-test-subj="hasTimeFieldCheckbox"]').exists()).toBeFalsy();
expect(wrapper.find('[data-test-subj="executionTimeFieldSelect"]').exists()).toBeFalsy();
const indexComboBox = wrapper
.find(EuiComboBox)
.filter('[data-test-subj="connectorIndexesComboBox"]');
// time field switch should show up if index has date type field mapping
setupGetFieldsResponse(true);
await act(async () => {
indexComboBox.prop('onChange')!([{ label: 'selection' }]);
await nextTick();
wrapper.update();
});
expect(wrapper.find('[data-test-subj="hasTimeFieldCheckbox"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="executionTimeFieldSelect"]').exists()).toBeFalsy();
expect(wrapper.find('[data-test-subj="connectorIndexesComboBox"]').length > 0).toBeTruthy();
expect(wrapper.find('[data-test-subj="indexRefreshCheckbox"]').length > 0).toBeTruthy();
// time field switch should go away if index does not has date type field mapping
setupGetFieldsResponse(false);
await act(async () => {
indexComboBox.prop('onChange')!([{ label: 'selection' }]);
await nextTick();
wrapper.update();
});
expect(wrapper.find('[data-test-subj="hasTimeFieldCheckbox"]').exists()).toBeFalsy();
expect(wrapper.find('[data-test-subj="executionTimeFieldSelect"]').exists()).toBeFalsy();
const indexSearchBoxValue = wrapper.find('[data-test-subj="comboBoxSearchInput"]');
expect(indexSearchBoxValue.first().props().value).toEqual('');
// time field dropdown should show up if index has date type field mapping and time switch is clicked
setupGetFieldsResponse(true);
await act(async () => {
indexComboBox.prop('onChange')!([{ label: 'selection' }]);
await nextTick();
wrapper.update();
});
expect(wrapper.find('[data-test-subj="hasTimeFieldCheckbox"]').exists()).toBeTruthy();
const timeFieldSwitch = wrapper
.find(EuiSwitch)
.filter('[data-test-subj="hasTimeFieldCheckbox"]');
await act(async () => {
timeFieldSwitch.prop('onChange')!(({
target: { checked: true },
} as unknown) as EuiSwitchEvent);
await nextTick();
wrapper.update();
});
expect(wrapper.find('[data-test-subj="executionTimeFieldSelect"]').exists()).toBeTruthy();
});
const indexComboBox = wrapper.find('#indexConnectorSelectSearchBox');
indexComboBox.first().simulate('click');
const event = { target: { value: 'indexPattern1' } };
indexComboBox.find('input').first().simulate('change', event);
test('renders correctly when editing connector - no date type field mapping', async () => {
const indexName = 'index-no-date-fields';
const props = {
action: {
name: 'Index Connector for Index With No Date Type',
actionTypeId: '.index',
config: {
index: indexName,
refresh: false,
},
secrets: {},
} as EsIndexActionConnector,
editActionConfig: () => {},
editActionSecrets: () => {},
errors: { index: [] },
readOnly: false,
};
setupGetFieldsResponse(false);
const wrapper = await setup(props);
const indexSearchBoxValueBeforeEnterData = wrapper.find(
'[data-test-subj="comboBoxSearchInput"]'
);
expect(indexSearchBoxValueBeforeEnterData.first().props().value).toEqual('indexPattern1');
expect(wrapper.find('[data-test-subj="connectorIndexesComboBox"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="indexRefreshCheckbox"]').exists()).toBeTruthy();
const indexComboBoxClear = wrapper.find('[data-test-subj="comboBoxClearButton"]');
indexComboBoxClear.first().simulate('click');
// time related fields shouldn't show up
expect(wrapper.find('[data-test-subj="hasTimeFieldCheckbox"]').exists()).toBeFalsy();
expect(wrapper.find('[data-test-subj="executionTimeFieldSelect"]').exists()).toBeFalsy();
const indexSearchBoxValueAfterEnterData = wrapper.find(
'[data-test-subj="comboBoxSearchInput"]'
);
expect(indexSearchBoxValueAfterEnterData.first().props().value).toEqual('indexPattern1');
const indexComboBox = wrapper
.find(EuiComboBox)
.filter('[data-test-subj="connectorIndexesComboBox"]');
expect(indexComboBox.prop('selectedOptions')).toEqual([{ label: indexName, value: indexName }]);
const refreshSwitch = wrapper.find(EuiSwitch).filter('[data-test-subj="indexRefreshCheckbox"]');
expect(refreshSwitch.prop('checked')).toEqual(false);
});
test('renders correctly when editing connector - refresh set to true', async () => {
const indexName = 'index-no-date-fields';
const props = {
action: {
name: 'Index Connector for Index With No Date Type',
actionTypeId: '.index',
config: {
index: indexName,
refresh: true,
},
secrets: {},
} as EsIndexActionConnector,
editActionConfig: () => {},
editActionSecrets: () => {},
errors: { index: [] },
readOnly: false,
};
setupGetFieldsResponse(false);
const wrapper = await setup(props);
expect(wrapper.find('[data-test-subj="connectorIndexesComboBox"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="indexRefreshCheckbox"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="hasTimeFieldCheckbox"]').exists()).toBeFalsy();
expect(wrapper.find('[data-test-subj="executionTimeFieldSelect"]').exists()).toBeFalsy();
const indexComboBox = wrapper
.find(EuiComboBox)
.filter('[data-test-subj="connectorIndexesComboBox"]');
expect(indexComboBox.prop('selectedOptions')).toEqual([{ label: indexName, value: indexName }]);
const refreshSwitch = wrapper.find(EuiSwitch).filter('[data-test-subj="indexRefreshCheckbox"]');
expect(refreshSwitch.prop('checked')).toEqual(true);
});
test('renders correctly when editing connector - with date type field mapping but no time field selected', async () => {
const indexName = 'index-no-date-fields';
const props = {
action: {
name: 'Index Connector for Index With No Date Type',
actionTypeId: '.index',
config: {
index: indexName,
refresh: false,
},
secrets: {},
} as EsIndexActionConnector,
editActionConfig: () => {},
editActionSecrets: () => {},
errors: { index: [] },
readOnly: false,
};
setupGetFieldsResponse(true);
const wrapper = await setup(props);
expect(wrapper.find('[data-test-subj="connectorIndexesComboBox"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="indexRefreshCheckbox"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="hasTimeFieldCheckbox"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="executionTimeFieldSelect"]').exists()).toBeFalsy();
const indexComboBox = wrapper
.find(EuiComboBox)
.filter('[data-test-subj="connectorIndexesComboBox"]');
expect(indexComboBox.prop('selectedOptions')).toEqual([{ label: indexName, value: indexName }]);
const refreshSwitch = wrapper.find(EuiSwitch).filter('[data-test-subj="indexRefreshCheckbox"]');
expect(refreshSwitch.prop('checked')).toEqual(false);
const timeFieldSwitch = wrapper
.find(EuiSwitch)
.filter('[data-test-subj="hasTimeFieldCheckbox"]');
expect(timeFieldSwitch.prop('checked')).toEqual(false);
});
test('renders correctly when editing connector - with date type field mapping and selected time field', async () => {
const indexName = 'index-no-date-fields';
const props = {
action: {
name: 'Index Connector for Index With No Date Type',
actionTypeId: '.index',
config: {
index: indexName,
refresh: false,
executionTimeField: 'test1',
},
secrets: {},
} as EsIndexActionConnector,
editActionConfig: () => {},
editActionSecrets: () => {},
errors: { index: [] },
readOnly: false,
};
setupGetFieldsResponse(true);
const wrapper = await setup(props);
expect(wrapper.find('[data-test-subj="connectorIndexesComboBox"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="indexRefreshCheckbox"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="hasTimeFieldCheckbox"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="executionTimeFieldSelect"]').exists()).toBeTruthy();
const indexComboBox = wrapper
.find(EuiComboBox)
.filter('[data-test-subj="connectorIndexesComboBox"]');
expect(indexComboBox.prop('selectedOptions')).toEqual([{ label: indexName, value: indexName }]);
const refreshSwitch = wrapper.find(EuiSwitch).filter('[data-test-subj="indexRefreshCheckbox"]');
expect(refreshSwitch.prop('checked')).toEqual(false);
const timeFieldSwitch = wrapper
.find(EuiSwitch)
.filter('[data-test-subj="hasTimeFieldCheckbox"]');
expect(timeFieldSwitch.prop('checked')).toEqual(true);
const timeFieldSelect = wrapper
.find(EuiSelect)
.filter('[data-test-subj="executionTimeFieldSelect"]');
expect(timeFieldSelect.prop('value')).toEqual('test1');
});
});

View file

@ -30,29 +30,45 @@ import {
} from '../../../../common/index_controls';
import { useKibana } from '../../../../common/lib/kibana';
interface TimeFieldOptions {
value: string;
text: string;
}
const IndexActionConnectorFields: React.FunctionComponent<
ActionConnectorFieldsProps<EsIndexActionConnector>
> = ({ action, editActionConfig, errors, readOnly }) => {
const { http, docLinks } = useKibana().services;
const { index, refresh, executionTimeField } = action.config;
const [hasTimeFieldCheckbox, setTimeFieldCheckboxState] = useState<boolean>(
const [showTimeFieldCheckbox, setShowTimeFieldCheckboxState] = useState<boolean>(
executionTimeField != null
);
const [hasTimeFieldCheckbox, setHasTimeFieldCheckboxState] = useState<boolean>(
executionTimeField != null
);
const [indexPatterns, setIndexPatterns] = useState([]);
const [indexOptions, setIndexOptions] = useState<EuiComboBoxOptionOption[]>([]);
const [timeFieldOptions, setTimeFieldOptions] = useState<Array<{ value: string; text: string }>>([
firstFieldOption,
]);
const [timeFieldOptions, setTimeFieldOptions] = useState<TimeFieldOptions[]>([]);
const [isIndiciesLoading, setIsIndiciesLoading] = useState<boolean>(false);
const setTimeFields = (fields: TimeFieldOptions[]) => {
if (fields.length > 0) {
setShowTimeFieldCheckboxState(true);
setTimeFieldOptions([firstFieldOption, ...fields]);
} else {
setHasTimeFieldCheckboxState(false);
setShowTimeFieldCheckboxState(false);
setTimeFieldOptions([]);
}
};
useEffect(() => {
const indexPatternsFunction = async () => {
setIndexPatterns(await getIndexPatterns());
if (index) {
const currentEsFields = await getFields(http!, [index]);
const timeFields = getTimeFieldOptions(currentEsFields as any);
setTimeFieldOptions([firstFieldOption, ...timeFields]);
setTimeFields(getTimeFieldOptions(currentEsFields as any));
}
};
indexPatternsFunction();
@ -123,13 +139,11 @@ const IndexActionConnectorFields: React.FunctionComponent<
// reset time field and expression fields if indices are deleted
if (indices.length === 0) {
setTimeFieldOptions([]);
setTimeFields([]);
return;
}
const currentEsFields = await getFields(http!, indices);
const timeFields = getTimeFieldOptions(currentEsFields as any);
setTimeFieldOptions([firstFieldOption, ...timeFields]);
setTimeFields(getTimeFieldOptions(currentEsFields as any));
}}
onSearchChange={async (search) => {
setIsIndiciesLoading(true);
@ -172,38 +186,40 @@ const IndexActionConnectorFields: React.FunctionComponent<
}
/>
<EuiSpacer size="m" />
<EuiSwitch
data-test-subj="hasTimeFieldCheckbox"
checked={hasTimeFieldCheckbox || false}
disabled={readOnly}
onChange={() => {
setTimeFieldCheckboxState(!hasTimeFieldCheckbox);
// if changing from checked to not checked (hasTimeField === true),
// set time field to null
if (hasTimeFieldCheckbox) {
editActionConfig('executionTimeField', null);
{showTimeFieldCheckbox && (
<EuiSwitch
data-test-subj="hasTimeFieldCheckbox"
checked={hasTimeFieldCheckbox || false}
disabled={readOnly}
onChange={() => {
setHasTimeFieldCheckboxState(!hasTimeFieldCheckbox);
// if changing from checked to not checked (hasTimeField === true),
// set time field to null
if (hasTimeFieldCheckbox) {
editActionConfig('executionTimeField', null);
}
}}
label={
<>
<FormattedMessage
id="xpack.triggersActionsUI.components.builtinActionTypes.indexAction.defineTimeFieldLabel"
defaultMessage="Define time field for each document"
/>
<EuiIconTip
position="right"
type="questionInCircle"
content={i18n.translate(
'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.definedateFieldTooltip',
{
defaultMessage: `Set this time field to the time the document was indexed.`,
}
)}
/>
</>
}
}}
label={
<>
<FormattedMessage
id="xpack.triggersActionsUI.components.builtinActionTypes.indexAction.defineTimeFieldLabel"
defaultMessage="Define time field for each document"
/>
<EuiIconTip
position="right"
type="questionInCircle"
content={i18n.translate(
'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.definedateFieldTooltip',
{
defaultMessage: `Automatically add a time field to each document when it's indexed.`,
}
)}
/>
</>
}
/>
{hasTimeFieldCheckbox ? (
/>
)}
{hasTimeFieldCheckbox && (
<>
<EuiSpacer size="m" />
<EuiFormRow
@ -233,7 +249,7 @@ const IndexActionConnectorFields: React.FunctionComponent<
/>
</EuiFormRow>
</>
) : null}
)}
</>
);
};