Skip to content

Commit 517e575

Browse files
[9.2] [ResponseOps][Rules] Bug - "Copy Query" button for ES Query Rule type not working (#253731) (#254525)
# Backport This will backport the following commits from `main` to `9.2`: - [[ResponseOps][Rules] Bug - "Copy Query" button for ES Query Rule type not working (#253731)](#253731) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Georgiana-Andreea Onoleață","email":"georgiana.onoleata@elastic.co"},"sourceCommit":{"committedDate":"2026-02-23T15:15:16Z","message":"[ResponseOps][Rules] Bug - \"Copy Query\" button for ES Query Rule type not working (#253731)\n\nCloses https://github.com/elastic/response-ops-team/issues/512\n\n## Summary\n\n- fixed `Copy query` button to handle invalid queries. Previously,\nclicking `Copy query` with an invalid KQL query (e.g., syntax errors)\nwould throw an uncaught exception to the console. Now it displays an\nerror message in the UI, consistent with how `Test query` handles\nerrors.\n- Additionally, only one error is shown at a time - clicking either\nbutton clears the other's error state.","sha":"eaa38eefa3e0fecf1c4ccee1eeffb3650276ed2f","branchLabelMapping":{"^v9.4.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:ResponseOps","backport:version","v9.4.0","v8.19.13","v9.2.7","v9.3.2"],"title":"[ResponseOps][Rules] Bug - \"Copy Query\" button for ES Query Rule type not working ","number":253731,"url":"https://github.com/elastic/kibana/pull/253731","mergeCommit":{"message":"[ResponseOps][Rules] Bug - \"Copy Query\" button for ES Query Rule type not working (#253731)\n\nCloses https://github.com/elastic/response-ops-team/issues/512\n\n## Summary\n\n- fixed `Copy query` button to handle invalid queries. Previously,\nclicking `Copy query` with an invalid KQL query (e.g., syntax errors)\nwould throw an uncaught exception to the console. Now it displays an\nerror message in the UI, consistent with how `Test query` handles\nerrors.\n- Additionally, only one error is shown at a time - clicking either\nbutton clears the other's error state.","sha":"eaa38eefa3e0fecf1c4ccee1eeffb3650276ed2f"}},"sourceBranch":"main","suggestedTargetBranches":["8.19","9.2","9.3"],"targetPullRequestStates":[{"branch":"main","label":"v9.4.0","branchLabelMappingKey":"^v9.4.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/253731","number":253731,"mergeCommit":{"message":"[ResponseOps][Rules] Bug - \"Copy Query\" button for ES Query Rule type not working (#253731)\n\nCloses https://github.com/elastic/response-ops-team/issues/512\n\n## Summary\n\n- fixed `Copy query` button to handle invalid queries. Previously,\nclicking `Copy query` with an invalid KQL query (e.g., syntax errors)\nwould throw an uncaught exception to the console. Now it displays an\nerror message in the UI, consistent with how `Test query` handles\nerrors.\n- Additionally, only one error is shown at a time - clicking either\nbutton clears the other's error state.","sha":"eaa38eefa3e0fecf1c4ccee1eeffb3650276ed2f"}},{"branch":"8.19","label":"v8.19.13","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"9.2","label":"v9.2.7","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"9.3","label":"v9.3.2","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Georgiana-Andreea Onoleață <georgiana.onoleata@elastic.co>
1 parent 83d8967 commit 517e575

File tree

4 files changed

+176
-8
lines changed

4 files changed

+176
-8
lines changed

x-pack/platform/plugins/shared/stack_alerts/public/rule_types/es_query/test_query_row/test_query_row.test.tsx

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,119 @@ describe('TestQueryRow', () => {
8484
expect(localOnCopyQuery).toHaveBeenCalled();
8585
expect(copyToClipboard).toHaveBeenCalledWith(COPIED_QUERY);
8686
});
87+
88+
it('should display an error when copyQuery throws an error', async () => {
89+
const errorMessage = 'Expected AND, OR, end of input but ":" found.';
90+
const localOnCopyQuery = jest.fn(() => {
91+
throw new Error(errorMessage);
92+
});
93+
const component = mountWithIntl(
94+
<TestQueryRow fetch={onFetch} copyQuery={localOnCopyQuery} hasValidationErrors={false} />
95+
);
96+
await act(async () => {
97+
findTestSubject(component, 'copyQuery').simulate('click');
98+
});
99+
component.update();
100+
expect(localOnCopyQuery).toHaveBeenCalled();
101+
expect(findTestSubject(component, 'copyQueryError').exists()).toBe(true);
102+
expect(findTestSubject(component, 'copyQueryError').text()).toContain(errorMessage);
103+
});
104+
105+
it('should clear copyQuery error when clicking copy query again', async () => {
106+
const errorMessage = 'Expected AND, OR, end of input but ":" found.';
107+
let shouldThrow = true;
108+
const localOnCopyQuery = jest.fn(() => {
109+
if (shouldThrow) {
110+
throw new Error(errorMessage);
111+
}
112+
return COPIED_QUERY;
113+
});
114+
const component = mountWithIntl(
115+
<TestQueryRow fetch={onFetch} copyQuery={localOnCopyQuery} hasValidationErrors={false} />
116+
);
117+
118+
await act(async () => {
119+
findTestSubject(component, 'copyQuery').simulate('click');
120+
});
121+
component.update();
122+
expect(findTestSubject(component, 'copyQueryError').exists()).toBe(true);
123+
124+
shouldThrow = false;
125+
await act(async () => {
126+
findTestSubject(component, 'copyQuery').simulate('click');
127+
});
128+
component.update();
129+
expect(findTestSubject(component, 'copyQueryError').exists()).toBe(false);
130+
});
131+
132+
it('should clear copyQuery error when clicking test query', async () => {
133+
const errorMessage = 'Expected AND, OR, end of input but ":" found.';
134+
const localOnCopyQuery = jest.fn(() => {
135+
throw new Error(errorMessage);
136+
});
137+
const component = mountWithIntl(
138+
<TestQueryRow fetch={onFetch} copyQuery={localOnCopyQuery} hasValidationErrors={false} />
139+
);
140+
141+
await act(async () => {
142+
findTestSubject(component, 'copyQuery').simulate('click');
143+
});
144+
component.update();
145+
expect(findTestSubject(component, 'copyQueryError').exists()).toBe(true);
146+
147+
await act(async () => {
148+
findTestSubject(component, 'testQuery').simulate('click');
149+
});
150+
component.update();
151+
expect(findTestSubject(component, 'copyQueryError').exists()).toBe(false);
152+
});
153+
154+
it('should clear testQuery error when clicking copy query', async () => {
155+
const localOnFetch = jest.fn(() => Promise.reject(new Error('Test query failed')));
156+
const component = mountWithIntl(
157+
<TestQueryRow fetch={localOnFetch} copyQuery={onCopyQuery} hasValidationErrors={false} />
158+
);
159+
160+
await act(async () => {
161+
findTestSubject(component, 'testQuery').simulate('click');
162+
});
163+
component.update();
164+
expect(findTestSubject(component, 'testQueryError').exists()).toBe(true);
165+
166+
await act(async () => {
167+
findTestSubject(component, 'copyQuery').simulate('click');
168+
});
169+
component.update();
170+
expect(findTestSubject(component, 'testQueryError').exists()).toBe(false);
171+
});
172+
173+
it('should clear copyQuery error when fetch prop changes', async () => {
174+
const errorMessage = 'Expected AND, OR, end of input but ":" found.';
175+
const localOnCopyQuery = jest.fn(() => {
176+
throw new Error(errorMessage);
177+
});
178+
const component = mountWithIntl(
179+
<TestQueryRow fetch={onFetch} copyQuery={localOnCopyQuery} hasValidationErrors={false} />
180+
);
181+
182+
await act(async () => {
183+
findTestSubject(component, 'copyQuery').simulate('click');
184+
});
185+
component.update();
186+
expect(findTestSubject(component, 'copyQueryError').exists()).toBe(true);
187+
188+
const newFetch = () =>
189+
Promise.resolve({
190+
testResults: {
191+
results: [{ group: 'all documents', hits: [], count: 10, sourceFields: [] }],
192+
truncated: false,
193+
},
194+
isGrouped: false,
195+
timeWindow: '10m',
196+
});
197+
198+
component.setProps({ fetch: newFetch });
199+
component.update();
200+
expect(findTestSubject(component, 'copyQueryError').exists()).toBe(false);
201+
});
87202
});

x-pack/platform/plugins/shared/stack_alerts/public/rule_types/es_query/test_query_row/test_query_row.tsx

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77
import type { ReactNode } from 'react';
8-
import React, { useState } from 'react';
8+
import React, { useState, useEffect } from 'react';
99
import {
1010
copyToClipboard,
1111
EuiButton,
@@ -19,6 +19,7 @@ import {
1919
EuiCallOut,
2020
} from '@elastic/eui';
2121
import { FormattedMessage } from '@kbn/i18n-react';
22+
import { i18n } from '@kbn/i18n';
2223
import type { ParsedAggregationResults } from '@kbn/triggers-actions-ui-plugin/common';
2324
import { useTestQuery } from './use_test_query';
2425
import { TestQueryRowTable } from './test_query_row_table';
@@ -42,6 +43,7 @@ export const TestQueryRow: React.FC<TestQueryRowProps> = ({
4243
}) => {
4344
const {
4445
onTestQuery,
46+
resetTestQueryResponse,
4547
testQueryResult,
4648
testQueryError,
4749
testQueryWarning,
@@ -50,6 +52,11 @@ export const TestQueryRow: React.FC<TestQueryRowProps> = ({
5052
} = useTestQuery(fetch);
5153

5254
const [copiedMessage, setCopiedMessage] = useState<ReactNode | null>(null);
55+
const [copyQueryError, setCopyQueryError] = useState<string | null>(null);
56+
57+
useEffect(() => {
58+
setCopyQueryError(null);
59+
}, [fetch]);
5360

5461
return (
5562
<>
@@ -62,6 +69,7 @@ export const TestQueryRow: React.FC<TestQueryRowProps> = ({
6269
iconSide="left"
6370
iconType="playFilled"
6471
onClick={() => {
72+
setCopyQueryError(null);
6573
onTestQuery();
6674
}}
6775
disabled={hasValidationErrors}
@@ -88,13 +96,24 @@ export const TestQueryRow: React.FC<TestQueryRowProps> = ({
8896
iconSide="left"
8997
iconType="copyClipboard"
9098
onClick={() => {
91-
const copied = copyToClipboard(copyQuery());
92-
if (copied) {
93-
setCopiedMessage(
94-
<FormattedMessage
95-
id="xpack.stackAlerts.esQuery.ui.queryCopiedToClipboard"
96-
defaultMessage="Copied"
97-
/>
99+
setCopyQueryError(null);
100+
resetTestQueryResponse();
101+
try {
102+
const copied = copyToClipboard(copyQuery());
103+
if (copied) {
104+
setCopiedMessage(
105+
<FormattedMessage
106+
id="xpack.stackAlerts.esQuery.ui.queryCopiedToClipboard"
107+
defaultMessage="Copied"
108+
/>
109+
);
110+
}
111+
} catch (err) {
112+
setCopyQueryError(
113+
i18n.translate('xpack.stackAlerts.esQuery.ui.copyQueryError', {
114+
defaultMessage: 'Error copying query: {message}',
115+
values: { message: err.message },
116+
})
98117
);
99118
}
100119
}}
@@ -138,6 +157,13 @@ export const TestQueryRow: React.FC<TestQueryRowProps> = ({
138157
</EuiText>
139158
</EuiFormRow>
140159
)}
160+
{copyQueryError && (
161+
<EuiFormRow>
162+
<EuiText data-test-subj="copyQueryError" color="danger" size="s">
163+
<p>{copyQueryError}</p>
164+
</EuiText>
165+
</EuiFormRow>
166+
)}
141167
{testQueryWarning && (
142168
<EuiFormRow fullWidth>
143169
<EuiCallOut color="warning" size="s" title={testQueryWarning} iconType="warning" />

x-pack/platform/plugins/shared/stack_alerts/public/rule_types/es_query/test_query_row/use_test_query.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,26 @@ describe('useTestQuery', () => {
160160
rows: [{ grouped: 'test' }],
161161
});
162162
});
163+
164+
test('resetTestQueryResponse clears all state', async () => {
165+
const errorMsg = 'How dare you writing such a query';
166+
const { result } = renderHook(useTestQuery, {
167+
initialProps: () => Promise.reject({ message: errorMsg }),
168+
});
169+
170+
await act(async () => {
171+
await result.current.onTestQuery();
172+
});
173+
expect(result.current.testQueryError).toContain(errorMsg);
174+
175+
act(() => {
176+
result.current.resetTestQueryResponse();
177+
});
178+
179+
expect(result.current.testQueryLoading).toBe(false);
180+
expect(result.current.testQueryError).toBe(null);
181+
expect(result.current.testQueryWarning).toBe(null);
182+
expect(result.current.testQueryResult).toBe(null);
183+
expect(result.current.testQueryPreview).toBe(null);
184+
});
163185
});

x-pack/platform/plugins/shared/stack_alerts/public/rule_types/es_query/test_query_row/use_test_query.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,13 @@ export function useTestQuery(fetch: () => Promise<TestQueryFetchResponse>) {
126126
}
127127
}, [fetch]);
128128

129+
const resetTestQueryResponse = useCallback(() => {
130+
setTestQueryResponse(TEST_QUERY_INITIAL_RESPONSE);
131+
}, []);
132+
129133
return {
130134
onTestQuery,
135+
resetTestQueryResponse,
131136
testQueryResult: testQueryResponse.result,
132137
testQueryError: testQueryResponse.error,
133138
testQueryWarning: testQueryResponse.warning,

0 commit comments

Comments
 (0)