[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:
parent
f945f3a425
commit
bb109b533c
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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}
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue