[ResponseOps] Do not change time field when edit (#206858)

Fixed: https://github.com/elastic/kibana/issues/204432

How to test:
Go to rules creation. Create ESquery rule. Set some `@timestamp` time
field. Try to edit rule. Check if it's save previous value of time
field.

Check the PR satisfies following conditions. 

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Julia 2025-01-23 12:38:15 +01:00 committed by GitHub
parent 5c59f3bdcd
commit 993392bc4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 102 additions and 30 deletions

View file

@ -6,9 +6,8 @@
*/
import React, { PropsWithChildren } from 'react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { fireEvent, render, waitFor, screen, act } from '@testing-library/react';
import { I18nProvider } from '@kbn/i18n-react';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
@ -24,6 +23,40 @@ const { hasExpressionValidationErrors } = jest.requireMock('../validation');
jest.mock('@kbn/esql-editor', () => ({
fetchFieldsFromESQL: jest.fn(),
}));
jest.mock('@kbn/triggers-actions-ui-plugin/public', () => {
const module = jest.requireActual('@kbn/kibana-react-plugin/public');
return {
...module,
getFields: jest.fn().mockResolvedValue([]),
};
});
jest.mock('@kbn/triggers-actions-ui-plugin/public/common', () => {
const module = jest.requireActual('@kbn/triggers-actions-ui-plugin/public/common');
return {
...module,
getTimeOptions: jest.fn(),
firstFieldOption: { text: '@timestamp', value: '@timestamp' },
getTimeFieldOptions: jest.fn().mockReturnValue([
{ value: '@timestamp', text: '@timestamp' },
{ value: 'event.ingested', text: 'event.ingested' },
]),
};
});
jest.mock('@kbn/esql-utils', () => {
return {
getESQLResults: jest.fn().mockResolvedValue({}),
getIndexPattern: jest.fn(),
getIndexPatternFromESQLQuery: jest.fn().mockReturnValue('index1'),
getESQLAdHocDataview: jest
.fn()
.mockResolvedValue({ timeFieldName: '@timestamp', getIndexPattern: jest.fn() }),
formatESQLColumns: jest.fn().mockReturnValue([]),
};
});
const { fetchFieldsFromESQL } = jest.requireMock('@kbn/esql-editor');
const { getFields } = jest.requireMock('@kbn/triggers-actions-ui-plugin/public');
@ -58,6 +91,39 @@ describe('EsqlQueryRuleTypeExpression', () => {
hasExpressionValidationErrors.mockReturnValue(false);
});
test('should render EsqlQueryRuleTypeExpression with chosen time field', async () => {
await act(async () => {
render(
<EsqlQueryExpression
unifiedSearch={unifiedSearchMock}
ruleInterval="1m"
ruleThrottle="1m"
alertNotifyWhen="onThrottleInterval"
ruleParams={{
...defaultEsqlQueryExpressionParams,
timeField: 'event.ingested',
esqlQuery: { esql: 'FROM *' },
}}
setRuleParams={() => {}}
setRuleProperty={() => {}}
errors={{ esqlQuery: [], timeField: [], timeWindowSize: [] }}
data={dataMock}
dataViews={dataViewMock}
defaultActionGroupId=""
actionGroups={[]}
charts={chartsStartMock}
onChangeMetaData={() => {}}
/>,
{
wrapper: AppWrapper,
}
);
});
const timeFieldText = await screen.findByText('event.ingested');
expect(timeFieldText).toBeInTheDocument();
});
it('should render EsqlQueryRuleTypeExpression with expected components', () => {
const result = render(
<EsqlQueryExpression
@ -135,7 +201,8 @@ describe('EsqlQueryRuleTypeExpression', () => {
],
});
getFields.mockResolvedValue([]);
const result = render(
render(
<EsqlQueryExpression
unifiedSearch={unifiedSearchMock}
ruleInterval="1m"
@ -157,12 +224,12 @@ describe('EsqlQueryRuleTypeExpression', () => {
}
);
fireEvent.click(result.getByTestId('testQuery'));
fireEvent.click(screen.getByTestId('testQuery'));
await waitFor(() => expect(fetchFieldsFromESQL).toBeCalled());
expect(result.getByTestId('testQuerySuccess')).toBeInTheDocument();
expect(result.getByText('Query matched 1 documents in the last 15s.')).toBeInTheDocument();
expect(result.queryByTestId('testQueryError')).not.toBeInTheDocument();
expect(screen.getByTestId('testQuerySuccess')).toBeInTheDocument();
expect(screen.getByText('Query matched 1 documents in the last 15s.')).toBeInTheDocument();
expect(screen.queryByTestId('testQueryError')).not.toBeInTheDocument();
});
test('should show error message if Test Query is throws error', async () => {

View file

@ -59,7 +59,7 @@ export const EsqlQueryExpression: React.FC<
// The sourceFields param is ignored for the ES|QL type
sourceFields: [],
});
const [query, setQuery] = useState<AggregateQuery>({ esql: '' });
const [query, setQuery] = useState<AggregateQuery>(esqlQuery ?? { esql: '' });
const [timeFieldOptions, setTimeFieldOptions] = useState([firstFieldOption]);
const [detectedTimestamp, setDetectedTimestamp] = useState<string | undefined>(undefined);
const [isLoading, setIsLoading] = useState<boolean>(false);
@ -75,18 +75,13 @@ export const EsqlQueryExpression: React.FC<
[setRuleParams]
);
const setDefaultExpressionValues = async () => {
const setDefaultExpressionValues = () => {
setRuleProperty('params', currentRuleParams);
setQuery(esqlQuery ?? { esql: '' });
if (esqlQuery) {
if (esqlQuery.esql) {
refreshTimeFields(esqlQuery);
}
}
if (timeField) {
setTimeFieldOptions([firstFieldOption, { text: timeField, value: timeField }]);
if (esqlQuery?.esql) {
refreshTimeFields(esqlQuery);
}
};
useEffect(() => {
setDefaultExpressionValues();
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -154,20 +149,30 @@ export const EsqlQueryExpression: React.FC<
isServerless,
]);
const refreshTimeFields = async (q: AggregateQuery) => {
const esqlDataView = await getESQLAdHocDataview(q.esql, dataViews);
const indexPattern: string = esqlDataView.getIndexPattern();
const currentEsFields = await getFields(http, [indexPattern]);
const refreshTimeFields = useCallback(
async (q: AggregateQuery) => {
const fetchTimeFieldsData = async (queryObj: AggregateQuery) => {
try {
const esqlDataView = await getESQLAdHocDataview(queryObj.esql, dataViews);
const indexPattern: string = esqlDataView.getIndexPattern();
const currentEsFields = await getFields(http, [indexPattern]);
const newTimeFieldOptions = getTimeFieldOptions(currentEsFields);
const timestampField = esqlDataView.timeFieldName;
return { newTimeFieldOptions, timestampField };
} catch (e) {
return { newTimeFieldOptions: [], timestampField: undefined };
}
};
const timeFields = getTimeFieldOptions(currentEsFields);
setTimeFieldOptions([firstFieldOption, ...timeFields]);
const timestampField = esqlDataView.timeFieldName;
if (timestampField) {
setParam('timeField', timestampField);
}
setDetectedTimestamp(timestampField);
};
const { newTimeFieldOptions, timestampField } = await fetchTimeFieldsData(q);
setTimeFieldOptions([firstFieldOption, ...newTimeFieldOptions]);
if (!timeField && timestampField) {
setParam('timeField', timestampField);
}
setDetectedTimestamp(timestampField);
},
[timeField, setParam, dataViews, http]
);
return (
<Fragment>