Skip to content

Commit b9ca831

Browse files
[8.19] [Rules] Fix error message for missing data view in custom threshold (#227727) (#228319)
# Backport This will backport the following commits from `main` to `8.19`: - [[Rules] Fix error message for missing data view in custom threshold (#227727)](#227727) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Bailey Cash","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-07-16T20:42:35Z","message":"[Rules] Fix error message for missing data view in custom threshold (#227727)\n\n## Summary\n\nResolves #220855 \n\n\nhttps://github.com/user-attachments/assets/9e6fb2a5-7319-41c5-9609-873ce6c96b52","sha":"57f976d62f7b7ce798626d511745688001575b05","branchLabelMapping":{"^v9.2.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","Team:SharedUX","Team:obs-ux-management","backport:version","v9.1.0","v8.19.0","author:obs-ux-management","v9.2.0","v8.18.4","v9.0.4"],"title":"[Rules] Fix error message for missing data view in custom threshold","number":227727,"url":"https://github.com/elastic/kibana/pull/227727","mergeCommit":{"message":"[Rules] Fix error message for missing data view in custom threshold (#227727)\n\n## Summary\n\nResolves #220855 \n\n\nhttps://github.com/user-attachments/assets/9e6fb2a5-7319-41c5-9609-873ce6c96b52","sha":"57f976d62f7b7ce798626d511745688001575b05"}},"sourceBranch":"main","suggestedTargetBranches":["9.1","8.19","8.18","9.0"],"targetPullRequestStates":[{"branch":"9.1","label":"v9.1.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.19","label":"v8.19.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.2.0","branchLabelMappingKey":"^v9.2.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/227727","number":227727,"mergeCommit":{"message":"[Rules] Fix error message for missing data view in custom threshold (#227727)\n\n## Summary\n\nResolves #220855 \n\n\nhttps://github.com/user-attachments/assets/9e6fb2a5-7319-41c5-9609-873ce6c96b52","sha":"57f976d62f7b7ce798626d511745688001575b05"}},{"branch":"8.18","label":"v8.18.4","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"9.0","label":"v9.0.4","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Bailey Cash <[email protected]>
1 parent b0bcf1a commit b9ca831

File tree

2 files changed

+114
-85
lines changed

2 files changed

+114
-85
lines changed

x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ describe('Expression', () => {
276276
});
277277

278278
it('should show an error message when searchSource throws an error', async () => {
279-
const errorMessage = 'Error in searchSource create';
279+
const errorMessage = 'Error fetching search sourceCould not locate that data view (id: )';
280280
const kibanaMock = kibanaStartMock.startContract();
281281
useKibanaMock.mockReturnValue({
282282
...kibanaMock,
@@ -303,7 +303,7 @@ describe('Expression', () => {
303303
});
304304
const { wrapper } = await setup();
305305
expect(wrapper.find(`[data-test-subj="thresholdRuleExpressionError"]`).first().text()).toBe(
306-
errorMessage
306+
errorMessage + 'Click here to choose a new data view'
307307
);
308308
});
309309

x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx

Lines changed: 112 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
EuiSpacer,
2121
EuiTitle,
2222
} from '@elastic/eui';
23+
import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/public';
2324
import { ISearchSource, Query } from '@kbn/data-plugin/common';
2425
import { DataView } from '@kbn/data-views-plugin/common';
2526
import { DataViewBase } from '@kbn/es-query';
@@ -34,6 +35,7 @@ import {
3435
} from '@kbn/triggers-actions-ui-plugin/public';
3536

3637
import { COMPARATORS } from '@kbn/alerting-comparators';
38+
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
3739
import { useKibana } from '../../utils/kibana_react';
3840
import { Aggregators } from '../../../common/custom_threshold_rule/types';
3941
import { TimeUnitChar } from '../../../common/utils/formatters/duration';
@@ -70,6 +72,7 @@ export default function Expressions(props: Props) {
7072
data,
7173
dataViews,
7274
dataViewEditor,
75+
7376
unifiedSearch: {
7477
ui: { SearchBar },
7578
},
@@ -85,7 +88,8 @@ export default function Expressions(props: Props) {
8588
const [dataView, setDataView] = useState<DataView>();
8689
const [dataViewTimeFieldError, setDataViewTimeFieldError] = useState<string>();
8790
const [searchSource, setSearchSource] = useState<ISearchSource>();
88-
const [paramsError, setParamsError] = useState<Error>();
91+
const [triggerResetDataView, setTriggerResetDataView] = useState<boolean>(false);
92+
const [paramsError, setParamsError] = useState<SavedObjectNotFound>();
8993
const [paramsWarning, setParamsWarning] = useState<string>();
9094
const [isNoDataChecked, setIsNoDataChecked] = useState<boolean>(
9195
(hasGroupBy && !!ruleParams.alertOnGroupDisappear) ||
@@ -99,77 +103,78 @@ export default function Expressions(props: Props) {
99103
[dataView]
100104
);
101105

102-
useEffect(() => {
103-
const initSearchSource = async () => {
104-
let initialSearchConfiguration = ruleParams.searchConfiguration;
105-
106-
if (!ruleParams.searchConfiguration || !ruleParams.searchConfiguration.index) {
107-
if (metadata?.currentOptions?.searchConfiguration) {
108-
initialSearchConfiguration = {
109-
query: {
110-
query: ruleParams.searchConfiguration?.query ?? '',
111-
language: 'kuery',
112-
},
113-
...metadata.currentOptions.searchConfiguration,
114-
};
115-
} else {
116-
const newSearchSource = data.search.searchSource.createEmpty();
117-
newSearchSource.setField('query', data.query.queryString.getDefaultQuery());
118-
const defaultDataView = await data.dataViews.getDefaultDataView();
119-
if (defaultDataView) {
120-
newSearchSource.setField('index', defaultDataView);
121-
setDataView(defaultDataView);
122-
}
123-
initialSearchConfiguration = getSearchConfiguration(
124-
newSearchSource.getSerializedFields(),
125-
setParamsWarning
126-
);
106+
const initSearchSource = async (resetDataView: boolean, thisData: DataPublicPluginStart) => {
107+
let initialSearchConfiguration = resetDataView ? undefined : ruleParams.searchConfiguration;
108+
if (!initialSearchConfiguration || !initialSearchConfiguration.index) {
109+
if (!resetDataView && metadata?.currentOptions?.searchConfiguration) {
110+
initialSearchConfiguration = {
111+
query: {
112+
query: ruleParams.searchConfiguration?.query ?? '',
113+
language: 'kuery',
114+
},
115+
...metadata.currentOptions.searchConfiguration,
116+
};
117+
} else {
118+
const newSearchSource = thisData.search.searchSource.createEmpty();
119+
newSearchSource.setField('query', thisData.query.queryString.getDefaultQuery());
120+
const defaultDataView = await thisData.dataViews.getDefaultDataView();
121+
if (defaultDataView) {
122+
newSearchSource.setField('index', defaultDataView);
123+
setDataView(defaultDataView);
127124
}
125+
initialSearchConfiguration = getSearchConfiguration(
126+
newSearchSource.getSerializedFields(),
127+
setParamsWarning
128+
);
128129
}
130+
}
129131

130-
try {
131-
const createdSearchSource = await data.search.searchSource.create(
132-
initialSearchConfiguration
133-
);
134-
setRuleParams(
135-
'searchConfiguration',
136-
getSearchConfiguration(
137-
{
138-
...initialSearchConfiguration,
139-
...(ruleParams.searchConfiguration?.query && {
132+
try {
133+
const createdSearchSource = await thisData.search.searchSource.create(
134+
initialSearchConfiguration
135+
);
136+
setRuleParams(
137+
'searchConfiguration',
138+
getSearchConfiguration(
139+
{
140+
...initialSearchConfiguration,
141+
...(!resetDataView &&
142+
ruleParams.searchConfiguration?.query && {
140143
query: ruleParams.searchConfiguration.query,
141144
}),
142-
},
143-
setParamsWarning
144-
)
145-
);
146-
setSearchSource(createdSearchSource);
147-
setDataView(createdSearchSource.getField('index'));
148-
149-
if (createdSearchSource.getField('index')) {
150-
const timeFieldName = createdSearchSource.getField('index')?.timeFieldName;
151-
if (!timeFieldName) {
152-
setDataViewTimeFieldError(
153-
i18n.translate(
154-
'xpack.observability.customThreshold.rule.alertFlyout.dataViewError.noTimestamp',
155-
{
156-
defaultMessage:
157-
'The selected data view does not have a timestamp field, please select another data view.',
158-
}
159-
)
160-
);
161-
} else {
162-
setDataViewTimeFieldError(undefined);
163-
}
145+
},
146+
setParamsWarning
147+
)
148+
);
149+
setSearchSource(createdSearchSource);
150+
setDataView(createdSearchSource.getField('index'));
151+
152+
if (createdSearchSource.getField('index')) {
153+
const timeFieldName = createdSearchSource.getField('index')?.timeFieldName;
154+
if (!timeFieldName) {
155+
setDataViewTimeFieldError(
156+
i18n.translate(
157+
'xpack.observability.customThreshold.rule.alertFlyout.dataViewError.noTimestamp',
158+
{
159+
defaultMessage:
160+
'The selected data view does not have a timestamp field, please select another data view.',
161+
}
162+
)
163+
);
164164
} else {
165165
setDataViewTimeFieldError(undefined);
166166
}
167-
} catch (error) {
168-
setParamsError(error);
167+
} else {
168+
setDataViewTimeFieldError(undefined);
169169
}
170-
};
170+
setParamsError(undefined);
171+
} catch (error) {
172+
setParamsError(error);
173+
}
174+
};
171175

172-
initSearchSource();
176+
useEffect(() => {
177+
initSearchSource(false, data);
173178
// eslint-disable-next-line react-hooks/exhaustive-deps
174179
}, [data.search.searchSource, data.dataViews, dataView]);
175180

@@ -348,18 +353,7 @@ export default function Expressions(props: Props) {
348353
}
349354
}, [metadata, setRuleParams]);
350355

351-
if (paramsError) {
352-
return (
353-
<>
354-
<EuiCallOut color="danger" iconType="warning" data-test-subj="thresholdRuleExpressionError">
355-
<p>{paramsError.message}</p>
356-
</EuiCallOut>
357-
<EuiSpacer size={'m'} />
358-
</>
359-
);
360-
}
361-
362-
if (!searchSource) {
356+
if (!paramsError && !searchSource) {
363357
return (
364358
<>
365359
<EuiEmptyPrompt title={<EuiLoadingSpinner size="xl" />} />
@@ -374,6 +368,7 @@ export default function Expressions(props: Props) {
374368
defaultMessage: 'Search for observability data… (e.g. host.name:host-1)',
375369
}
376370
);
371+
377372
return (
378373
<>
379374
{!!paramsWarning && (
@@ -403,15 +398,49 @@ export default function Expressions(props: Props) {
403398
</h5>
404399
</EuiTitle>
405400
<EuiSpacer size="s" />
406-
<DataViewSelectPopover
407-
dependencies={{ dataViews, dataViewEditor }}
408-
dataView={dataView}
409-
metadata={{ adHocDataViewList: metadata?.adHocDataViewList || [] }}
410-
onSelectDataView={onSelectDataView}
411-
onChangeMetaData={({ adHocDataViewList }) => {
412-
onChangeMetaData({ ...metadata, adHocDataViewList });
413-
}}
414-
/>
401+
{paramsError && !triggerResetDataView ? (
402+
<EuiCallOut color="danger" iconType="warning" data-test-subj="thresholdRuleExpressionError">
403+
<p>
404+
{i18n.translate('xpack.observability.customThreshold.rule.alertFlyout.error.message', {
405+
defaultMessage: 'Error fetching search source',
406+
})}
407+
<br />
408+
{i18n.translate(
409+
'xpack.observability.customThreshold.rule.alertFlyout.error.messageDescription',
410+
{
411+
defaultMessage: 'Could not locate that data view (id: {id})',
412+
values: { id: paramsError?.savedObjectId },
413+
}
414+
)}
415+
<br />
416+
<EuiButtonEmpty
417+
data-test-subj="thresholdRuleExpressionErrorButton"
418+
flush="left"
419+
onClick={() => {
420+
initSearchSource(true, data);
421+
setTriggerResetDataView(true);
422+
}}
423+
>
424+
{i18n.translate(
425+
'xpack.observability.customThreshold.rule.alertFlyout.error.message',
426+
{
427+
defaultMessage: 'Click here to choose a new data view',
428+
}
429+
)}
430+
</EuiButtonEmpty>
431+
</p>
432+
</EuiCallOut>
433+
) : (
434+
<DataViewSelectPopover
435+
dependencies={{ dataViews, dataViewEditor }}
436+
dataView={dataView}
437+
metadata={{ adHocDataViewList: metadata?.adHocDataViewList || [] }}
438+
onSelectDataView={onSelectDataView}
439+
onChangeMetaData={({ adHocDataViewList }) => {
440+
onChangeMetaData({ ...metadata, adHocDataViewList });
441+
}}
442+
/>
443+
)}
415444
{dataViewTimeFieldError && (
416445
<EuiFormErrorText data-test-subj="thresholdRuleDataViewErrorNoTimestamp">
417446
{dataViewTimeFieldError}

0 commit comments

Comments
 (0)