diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 97b0424168f0a..9153d851b70a7 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -180,6 +180,7 @@ const StatefulEventsViewerComponent: React.FC = ({ bulkActions, columns, dataProviders, + dataViewId: selectedDataViewId || undefined, defaultCellActions, deletedEventIds, disabledCellActions: FIELDS_WITHOUT_CELL_ACTIONS, diff --git a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx index 1abef58feeade..1f9fceb4697b1 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx @@ -101,6 +101,7 @@ export interface TGridIntegratedProps { createFieldComponent?: CreateFieldComponentType; data?: DataPublicPluginStart; dataProviders: DataProvider[]; + dataViewId?: string; defaultCellActions?: TGridCellAction[]; deletedEventIds: Readonly; disabledCellActions: string[]; @@ -145,6 +146,7 @@ const TGridIntegratedComponent: React.FC = ({ columns, data, dataProviders, + dataViewId, defaultCellActions, deletedEventIds, disabledCellActions, @@ -236,6 +238,7 @@ const TGridIntegratedComponent: React.FC = ({ // We rely on entityType to determine Events vs Alerts alertConsumers: SECURITY_ALERTS_CONSUMERS, data, + dataViewId, docValueFields, endDate: end, entityType, diff --git a/x-pack/plugins/timelines/public/container/index.tsx b/x-pack/plugins/timelines/public/container/index.tsx index 09f7e0f9c9448..d027d36df2751 100644 --- a/x-pack/plugins/timelines/public/container/index.tsx +++ b/x-pack/plugins/timelines/public/container/index.tsx @@ -21,6 +21,8 @@ import { import type { DataPublicPluginStart, IEsError } from '../../../../../src/plugins/data/public'; import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; +import { mountReactNode } from '../../../../../src/core/public/utils'; import { Direction, TimelineFactoryQueryTypes, @@ -43,8 +45,6 @@ import type { KueryFilterQueryKind } from '../../common/types/timeline'; import { useAppToasts } from '../hooks/use_app_toasts'; import { TimelineId } from '../store/t_grid/types'; import * as i18n from './translations'; -import { tGridActions } from '../store/t_grid'; -import { mountReactNode } from '../../../../../src/core/public/utils'; export type InspectResponse = Inspect & { response: string[] }; @@ -76,6 +76,7 @@ type TimelineResponse = TimelineEventsAllStrateg export interface UseTimelineEventsProps { alertConsumers?: AlertConsumers[]; data?: DataPublicPluginStart; + dataViewId?: string; docValueFields?: DocValueFields[]; endDate: string; entityType: EntityType; @@ -126,6 +127,7 @@ export const useTimelineEvents = ({ excludeEcsData = false, id = ID, indexNames, + dataViewId, fields, filterQuery, startDate, @@ -147,6 +149,7 @@ export const useTimelineEvents = ({ null ); const prevTimelineRequest = useRef | null>(null); + const { catchRuntimeFieldError } = useCatchRuntimeFieldError(dataViewId); const clearSignalsState = useCallback(() => { if (id != null && detectionsTimelineIds.some((timelineId) => timelineId === id)) { @@ -194,7 +197,7 @@ export const useTimelineEvents = ({ loadPage: wrappedLoadPage, updatedAt: 0, }); - const { addError, addWarning, api: toastsApi } = useAppToasts(); + const { addError, addWarning } = useAppToasts(); const timelineSearch = useCallback( (request: TimelineRequest | null) => { @@ -245,19 +248,7 @@ export const useTimelineEvents = ({ }, error: (msg) => { setLoading(false); - const runtimeFieldErrorReason = getRuntimeFieldErrorReason(msg.err); - if (runtimeFieldErrorReason) { - // show warning toast to reset the sort parameter, otherwise the timeline may become inaccessible - const toast = addWarning( - getRuntimeFieldSortWarningToast({ - text: runtimeFieldErrorReason, - resetSort: () => { - dispatch(tGridActions.updateSort({ id, sort: [] })); - toastsApi.remove(toast); - }, - }) - ); - } + catchRuntimeFieldError(msg.err); addError(msg, { title: i18n.FAIL_TIMELINE_EVENTS, }); @@ -272,7 +263,7 @@ export const useTimelineEvents = ({ asyncSearch(); refetch.current = asyncSearch; }, - [skip, data, entityType, setUpdated, addWarning, addError, toastsApi, dispatch, id] + [skip, data, entityType, setUpdated, addWarning, addError, catchRuntimeFieldError] ); useEffect(() => { @@ -395,10 +386,49 @@ export const useTimelineEvents = ({ return [loading, timelineResponse]; }; +export const useCatchRuntimeFieldError = (dataViewId?: string) => { + const { addWarning, api: toastsApi } = useAppToasts(); + const navigateToApp = useKibana().services.application?.navigateToApp; + + const catchRuntimeFieldError = useCallback( + (error: IEsError) => { + const runtimeFieldErrorReason = getRuntimeFieldErrorReason(error); + if (navigateToApp && dataViewId && runtimeFieldErrorReason) { + const toast = addWarning({ + iconType: 'alert', + title: i18n.ERROR_RUNTIME_FIELD_TIMELINE_EVENTS, + toastLifeTimeMs: 300000, + text: mountReactNode( + <> +

{runtimeFieldErrorReason}

+
+ { + navigateToApp('management', { + path: `kibana/dataViews/dataView/${dataViewId}`, + }); + toastsApi.remove(toast); + }} + > + {i18n.ERROR_RUNTIME_MANAGE_DATA_VIEW} + +
+ + ), + }); + } + }, + [addWarning, dataViewId, navigateToApp, toastsApi] + ); + return { catchRuntimeFieldError }; +}; + /** * Returns the reason string if the error is a runtime script missing field error */ -const getRuntimeFieldErrorReason = (error: IEsError): string | null => { +function getRuntimeFieldErrorReason(error: IEsError): string | null { const failedShards = error?.attributes?.caused_by?.failed_shards; if (failedShards && failedShards.length > 0) { const runtimeFieldFailedShard = failedShards?.find((failedShard) => @@ -409,26 +439,4 @@ const getRuntimeFieldErrorReason = (error: IEsError): string | null => { } } return null; -}; - -const getRuntimeFieldSortWarningToast = ({ - text, - resetSort, -}: { - text: string; - resetSort: () => void; -}) => ({ - iconType: 'alert', - title: i18n.ERROR_RUNTIME_FIELD_TIMELINE_EVENTS, - toastLifeTimeMs: 300000, - text: mountReactNode( - <> -

{text}

-
- - {i18n.ERROR_RUNTIME_FIELD_CLEAR_SORTING} - -
- - ), -}); +} diff --git a/x-pack/plugins/timelines/public/container/translations.ts b/x-pack/plugins/timelines/public/container/translations.ts index f4c7b3f894849..971db6534032d 100644 --- a/x-pack/plugins/timelines/public/container/translations.ts +++ b/x-pack/plugins/timelines/public/container/translations.ts @@ -22,15 +22,15 @@ export const FAIL_TIMELINE_EVENTS = i18n.translate( ); export const ERROR_RUNTIME_FIELD_TIMELINE_EVENTS = i18n.translate( - 'xpack.timelines.timelineEvents.errorRuntimeFieldSearchDescription', + 'xpack.timelines.timelineEvents.errorRuntimeFieldToastTitle', { defaultMessage: 'Runtime field error', } ); -export const ERROR_RUNTIME_FIELD_CLEAR_SORTING = i18n.translate( - 'xpack.timelines.timelineEvents.errorRuntimeFieldSearchButton', +export const ERROR_RUNTIME_MANAGE_DATA_VIEW = i18n.translate( + 'xpack.timelines.timelineEvents.errorRuntimeFieldToastButton', { - defaultMessage: 'Clear sorting', + defaultMessage: 'Manage Data View', } );