| undefined;
+ timelineId?: string | null;
+ toggleColumn?: (column: ColumnHeaderOptions) => void;
+ toggleTopN: () => void;
+ values?: string[] | string | null;
+}
+
+interface UseHoverActionItems {
+ overflowActionItems: JSX.Element[];
+ allActionItems: JSX.Element[];
+}
+
+export const useHoverActionItems = ({
+ dataProvider,
+ dataType,
+ defaultFocusedButtonRef,
+ draggableId,
+ enableOverflowButton,
+ field,
+ handleHoverActionClicked,
+ isObjectArray,
+ isOverflowPopoverOpen,
+ itemsToShow = 2,
+ onFilterAdded,
+ onOverflowButtonClick,
+ ownFocus,
+ showTopN,
+ stKeyboardEvent,
+ timelineId,
+ toggleColumn,
+ toggleTopN,
+ values,
+}: UseHoverActionItemsProps): UseHoverActionItems => {
+ const kibana = useKibana();
+ const { timelines } = kibana.services;
+ // Common actions used by the alert table and alert flyout
+ const {
+ getAddToTimelineButton,
+ getColumnToggleButton,
+ getCopyButton,
+ getFilterForValueButton,
+ getFilterOutValueButton,
+ getOverflowButton,
+ } = timelines.getHoverActions();
+
+ const filterManagerBackup = useMemo(() => kibana.services.data.query.filterManager, [
+ kibana.services.data.query.filterManager,
+ ]);
+ const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
+ const { filterManager: activeFilterMananager } = useDeepEqualSelector((state) =>
+ getManageTimeline(state, timelineId ?? '')
+ );
+ const filterManager = useMemo(
+ () => (timelineId === TimelineId.active ? activeFilterMananager : filterManagerBackup),
+ [timelineId, activeFilterMananager, filterManagerBackup]
+ );
+
+ // Regarding data from useManageTimeline:
+ // * `indexToAdd`, which enables the alerts index to be appended to
+ // the `indexPattern` returned by `useWithSource`, may only be populated when
+ // this component is rendered in the context of the active timeline. This
+ // behavior enables the 'All events' view by appending the alerts index
+ // to the index pattern.
+ const activeScope: SourcererScopeName =
+ timelineId === TimelineId.active
+ ? SourcererScopeName.timeline
+ : timelineId != null &&
+ [TimelineId.detectionsPage, TimelineId.detectionsRulesDetailsPage].includes(
+ timelineId as TimelineId
+ )
+ ? SourcererScopeName.detections
+ : SourcererScopeName.default;
+ const { browserFields } = useSourcererScope(activeScope);
+
+ /*
+ * In the case of `DisableOverflowButton`, we show filters only when topN is NOT opened. As after topN button is clicked, the chart panel replace current hover actions in the hover actions' popover, so we have to hide all the actions.
+ * in the case of `EnableOverflowButton`, we only need to hide all the items in the overflow popover as the chart's panel opens in the overflow popover, so non-overflowed actions are not affected.
+ */
+ const showFilters =
+ values != null && (enableOverflowButton || (!showTopN && !enableOverflowButton));
+
+ const allItems = useMemo(
+ () =>
+ [
+ showFilters ? (
+
+ {getFilterForValueButton({
+ defaultFocusedButtonRef,
+ field,
+ filterManager,
+ keyboardEvent: stKeyboardEvent,
+ onClick: handleHoverActionClicked,
+ onFilterAdded,
+ ownFocus,
+ showTooltip: enableOverflowButton ? false : true,
+ value: values,
+ })}
+
+ ) : null,
+ showFilters ? (
+
+ {getFilterOutValueButton({
+ field,
+ filterManager,
+ keyboardEvent: stKeyboardEvent,
+ onFilterAdded,
+ ownFocus,
+ onClick: handleHoverActionClicked,
+ showTooltip: enableOverflowButton ? false : true,
+ value: values,
+ })}
+
+ ) : null,
+ toggleColumn ? (
+
+ {getColumnToggleButton({
+ Component: enableOverflowButton ? EuiContextMenuItem : undefined,
+ field,
+ isDisabled: isObjectArray && dataType !== 'geo_point',
+ isObjectArray,
+ keyboardEvent: stKeyboardEvent,
+ ownFocus,
+ onClick: handleHoverActionClicked,
+ showTooltip: enableOverflowButton ? false : true,
+ toggleColumn,
+ value: values,
+ })}
+
+ ) : null,
+ values != null && (draggableId != null || !isEmpty(dataProvider)) ? (
+
+ {getAddToTimelineButton({
+ Component: enableOverflowButton ? EuiContextMenuItem : undefined,
+ dataProvider,
+ draggableId,
+ field,
+ keyboardEvent: stKeyboardEvent,
+ ownFocus,
+ onClick: handleHoverActionClicked,
+ showTooltip: enableOverflowButton ? false : true,
+ value: values,
+ })}
+
+ ) : null,
+ allowTopN({
+ browserField: getAllFieldsByName(browserFields)[field],
+ fieldName: field,
+ }) ? (
+
+ ) : null,
+ field != null ? (
+
+ {getCopyButton({
+ Component: enableOverflowButton ? EuiContextMenuItem : undefined,
+ field,
+ isHoverAction: true,
+ keyboardEvent: stKeyboardEvent,
+ ownFocus,
+ onClick: handleHoverActionClicked,
+ showTooltip: enableOverflowButton ? false : true,
+ value: values,
+ })}
+
+ ) : null,
+ ].filter((item) => {
+ return item != null;
+ }),
+ [
+ browserFields,
+ dataProvider,
+ dataType,
+ defaultFocusedButtonRef,
+ draggableId,
+ enableOverflowButton,
+ field,
+ filterManager,
+ getAddToTimelineButton,
+ getColumnToggleButton,
+ getCopyButton,
+ getFilterForValueButton,
+ getFilterOutValueButton,
+ handleHoverActionClicked,
+ isObjectArray,
+ onFilterAdded,
+ ownFocus,
+ showFilters,
+ showTopN,
+ stKeyboardEvent,
+ timelineId,
+ toggleColumn,
+ toggleTopN,
+ values,
+ ]
+ ) as JSX.Element[];
+
+ const overflowBtn = useMemo(
+ () => (
+
+ ),
+ [enableOverflowButton, field, onFilterAdded, ownFocus, showTopN, timelineId, toggleTopN, values]
+ );
+
+ const overflowActionItems = useMemo(
+ () =>
+ [
+ ...allItems.slice(0, itemsToShow),
+ ...(enableOverflowButton && itemsToShow > 0
+ ? [
+ getOverflowButton({
+ closePopOver: handleHoverActionClicked,
+ field,
+ keyboardEvent: stKeyboardEvent,
+ ownFocus,
+ onClick: onOverflowButtonClick,
+ showTooltip: enableOverflowButton ? false : true,
+ value: values,
+ items: showTopN ? [overflowBtn] : allItems.slice(itemsToShow),
+ isOverflowPopoverOpen: !!isOverflowPopoverOpen,
+ }),
+ ]
+ : []),
+ ].filter((item) => {
+ return item != null;
+ }),
+ [
+ allItems,
+ enableOverflowButton,
+ field,
+ getOverflowButton,
+ handleHoverActionClicked,
+ isOverflowPopoverOpen,
+ itemsToShow,
+ onOverflowButtonClick,
+ overflowBtn,
+ ownFocus,
+ showTopN,
+ stKeyboardEvent,
+ values,
+ ]
+ );
+
+ const allActionItems = useMemo(() => (showTopN ? [overflowBtn] : allItems), [
+ allItems,
+ overflowBtn,
+ showTopN,
+ ]);
+
+ return {
+ overflowActionItems,
+ allActionItems,
+ };
+};
diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_actions.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_actions.tsx
index 373f944b70a81..6e905572aaedb 100644
--- a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_actions.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_actions.tsx
@@ -77,6 +77,10 @@ export const useHoverActions = ({
});
}, [handleClosePopOverTrigger]);
+ const closeTopN = useCallback(() => {
+ setShowTopN(false);
+ }, []);
+
const hoverContent = useMemo(() => {
// display links as additional content in the hover menu to enable keyboard
// navigation of links (when the draggable contains them):
@@ -92,6 +96,7 @@ export const useHoverActions = ({
return (
);
}, [
+ closeTopN,
dataProvider,
handleClosePopOverTrigger,
hoverActionsOwnFocus,
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
index d8d6424ef2a73..dec8e3e83a1ab 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
@@ -383,6 +383,7 @@ export const AlertsTableComponent: React.FC = ({
pageFilters={defaultFiltersMemo}
defaultCellActions={defaultCellActions}
defaultModel={defaultTimelineModel}
+ entityType="alerts"
end={to}
currentFilter={filterGroup}
id={timelineId}
diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx
index f1187c007106a..077217b346a6b 100644
--- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx
@@ -181,6 +181,20 @@ export const TakeActionDropdown = React.memo(
[eventFilterActions, exceptionActions, isEvent, actionsData.ruleId]
);
+ const addToCaseProps = useMemo(() => {
+ if (ecsData) {
+ return {
+ event: { data: [], ecs: ecsData, _id: ecsData._id },
+ useInsertTimeline: insertTimelineHook,
+ casePermissions,
+ appId: 'securitySolution',
+ onClose: afterCaseSelection,
+ };
+ } else {
+ return null;
+ }
+ }, [afterCaseSelection, casePermissions, ecsData, insertTimelineHook]);
+
const panels = useMemo(() => {
if (tGridEnabled) {
return [
@@ -202,26 +216,8 @@ export const TakeActionDropdown = React.memo(
id: 2,
title: ACTION_ADD_TO_CASE,
content: [
- <>
- {ecsData &&
- timelinesUi.getAddToExistingCaseButton({
- ecsRowData: ecsData,
- useInsertTimeline: insertTimelineHook,
- casePermissions,
- appId: 'securitySolution',
- onClose: afterCaseSelection,
- })}
- >,
- <>
- {ecsData &&
- timelinesUi.getAddToNewCaseButton({
- ecsRowData: ecsData,
- useInsertTimeline: insertTimelineHook,
- casePermissions,
- appId: 'securitySolution',
- onClose: afterCaseSelection,
- })}
- >,
+ <>{addToCaseProps && timelinesUi.getAddToExistingCaseButton(addToCaseProps)}>,
+ <>{addToCaseProps && timelinesUi.getAddToNewCaseButton(addToCaseProps)}>,
],
},
];
@@ -239,16 +235,13 @@ export const TakeActionDropdown = React.memo(
];
}
}, [
+ addToCaseProps,
alertsActionItems,
hostIsolationAction,
investigateInTimelineAction,
- ecsData,
- casePermissions,
- insertTimelineHook,
timelineId,
timelinesUi,
actionItems,
- afterCaseSelection,
tGridEnabled,
]);
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx
index 2dc254f7a35ed..b19c680fb9197 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx
@@ -112,6 +112,7 @@ const EventsQueryTabBodyComponent: React.FC = ({
defaultCellActions={defaultCellActions}
defaultModel={eventsDefaultModel}
end={endDate}
+ entityType="events"
id={TimelineId.hostsPageEvents}
renderCellValue={DefaultCellRenderer}
rowRenderers={defaultRowRenderers}
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
index 063dcc44df2e8..ca5af088b36f6 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
@@ -105,18 +105,19 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory;
coreStart: CoreStart;
+ selectedEndpoint?: string;
+ store: ImmutableMiddlewareAPI;
}) {
const { getState, dispatch } = store;
dispatch({
@@ -703,11 +706,12 @@ async function endpointDetailsMiddleware({
type: 'serverCancelledEndpointListLoading',
});
}
- const { selected_endpoint: selectedEndpoint } = uiQueryParams(getState());
- if (selectedEndpoint !== undefined) {
- loadEndpointDetails({ store, coreStart, selectedEndpoint });
+ if (typeof selectedEndpoint === 'undefined') {
+ return;
}
+ await loadEndpointDetails({ store, coreStart, selectedEndpoint });
}
+
async function endpointDetailsActivityLogChangedMiddleware({
store,
coreStart,
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx
index f11d2872e3d26..b3a32f6518c91 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx
@@ -20,9 +20,11 @@ const DatePickerWrapper = styled.div`
background: white;
`;
const StickyFlexItem = styled(EuiFlexItem)`
+ max-width: 350px;
position: sticky;
top: ${(props) => props.theme.eui.euiSizeM};
z-index: 1;
+ padding: ${(props) => `0 ${props.theme.eui.paddingSizes.m}`};
`;
export const DateRangePicker = memo(() => {
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
index c054f7d06dde3..996198568ad27 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
@@ -876,6 +876,25 @@ describe('when on the endpoint list page', () => {
expect(emptyState).toBe(null);
expect(dateRangePicker).not.toBe(null);
});
+
+ it('should display activity log when tab is loaded using the URL', async () => {
+ const userChangedUrlChecker = middlewareSpy.waitForAction('userChangedUrl');
+ reactTestingLibrary.act(() => {
+ history.push(
+ `${MANAGEMENT_PATH}/endpoints?page_index=0&page_size=10&selected_endpoint=1&show=activity_log`
+ );
+ });
+ const changedUrlAction = await userChangedUrlChecker;
+ expect(changedUrlAction.payload.search).toEqual(
+ '?page_index=0&page_size=10&selected_endpoint=1&show=activity_log'
+ );
+ await middlewareSpy.waitForAction('endpointDetailsActivityLogChanged');
+ reactTestingLibrary.act(() => {
+ dispatchEndpointDetailsActivityLogChanged('success', getMockData());
+ });
+ const logEntries = await renderResult.queryAllByTestId('timelineEntry');
+ expect(logEntries.length).toEqual(2);
+ });
});
describe('when showing host Policy Response panel', () => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.tsx
index 1e081d249cc00..5059c62f61d9d 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.tsx
@@ -108,9 +108,14 @@ export const FieldName = React.memo<{
});
}, [handleClosePopOverTrigger]);
+ const closeTopN = useCallback(() => {
+ setShowTopN(false);
+ }, []);
+
const hoverContent = useMemo(
() => (
),
[
+ closeTopN,
fieldId,
handleClosePopOverTrigger,
hoverActionsOwnFocus,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap
index c8da44e648ff8..cf643b47c3de0 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap
@@ -261,7 +261,7 @@ Array [
-ms-flex: 1;
flex: 1;
overflow: hidden;
- padding: 16px;
+ padding: 0 16px 16px;
}
`${theme.eui.paddingSizes.m}`};
+ padding: ${({ theme }) => `0 ${theme.eui.paddingSizes.m} ${theme.eui.paddingSizes.m}`};
}
}
`;
@@ -161,7 +161,7 @@ const EventDetailsPanelComponent: React.FC = ({
return isFlyoutView ? (
<>
-
+
{isHostIsolationPanelOpen ? (
backToAlertDetailsLink
) : (
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx
index 803e688586a31..942eeac9417c2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx
@@ -108,7 +108,7 @@ const ActionsComponent: React.FC = ({
const addToCaseActionProps = useMemo(() => {
return {
ariaLabel: i18n.ATTACH_ALERT_TO_CASE_FOR_ROW({ ariaRowindex, columnValues }),
- ecsRowData: ecsData,
+ event: { data: [], ecs: ecsData, _id: ecsData._id },
useInsertTimeline: insertTimelineHook,
casePermissions,
appId: APP_ID,
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts
index d6660effde38c..2bfa5ae1aa3c4 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts
@@ -274,6 +274,14 @@ describe('Host Isolation', () => {
actionID
);
});
+ it('records the timeout in the action payload', async () => {
+ const ctx = await callRoute(ISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'] },
+ });
+ const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
+ .index as jest.Mock).mock.calls[0][0].body;
+ expect(actionDoc.timeout).toEqual(300);
+ });
it('succeeds when just an endpoint ID is provided', async () => {
await callRoute(ISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'] } });
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts
index fceb45b17c258..45f0e851dfdd1 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts
@@ -120,6 +120,7 @@ export const isolationRequestHandler = function (
input_type: 'endpoint',
agents: endpointData.map((endpt: HostMetadata) => endpt.elastic.agent.id),
user_id: user!.username,
+ timeout: 300, // 5 minutes
data: {
command: isolate ? 'isolate' : 'unisolate',
comment: req.body.comment ?? undefined,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md
index 059005707625f..893797afa44d7 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md
@@ -58,7 +58,7 @@ to any newly saved rule:
{
"name" : "param:exceptionsList_0",
"id" : "endpoint_list",
- "type" : "exception-list"
+ "type" : "exception-list-agnostic"
},
{
"name" : "param:exceptionsList_1",
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts
index 56a1e875ac5e7..75180c6eaee48 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts
@@ -8,8 +8,11 @@
import { extractExceptionsList } from './extract_exceptions_list';
import { loggingSystemMock } from 'src/core/server/mocks';
import { RuleParams } from '../../schemas/rule_schemas';
-import { EXCEPTION_LIST_NAMESPACE } from '@kbn/securitysolution-list-constants';
-import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils';
+import {
+ EXCEPTION_LIST_NAMESPACE,
+ EXCEPTION_LIST_NAMESPACE_AGNOSTIC,
+} from '@kbn/securitysolution-list-constants';
+import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils/constants';
describe('extract_exceptions_list', () => {
type FuncReturn = ReturnType;
@@ -48,21 +51,21 @@ describe('extract_exceptions_list', () => {
{
id: '123',
name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_0`,
- type: EXCEPTION_LIST_NAMESPACE,
+ type: EXCEPTION_LIST_NAMESPACE_AGNOSTIC,
},
]);
});
- test('It returns two exception lists transformed into a saved object references', () => {
+ test('It returns 2 exception lists transformed into a saved object references', () => {
const twoInputs: RuleParams['exceptionsList'] = [
mockExceptionsList()[0],
- { ...mockExceptionsList()[0], id: '976' },
+ { ...mockExceptionsList()[0], id: '976', namespace_type: 'single' },
];
expect(extractExceptionsList({ logger, exceptionsList: twoInputs })).toEqual([
{
id: '123',
name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_0`,
- type: EXCEPTION_LIST_NAMESPACE,
+ type: EXCEPTION_LIST_NAMESPACE_AGNOSTIC,
},
{
id: '976',
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.ts
index 9b7f8bbcefee1..93deae014005b 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.ts
@@ -6,7 +6,7 @@
*/
import { Logger, SavedObjectReference } from 'src/core/server';
-import { EXCEPTION_LIST_NAMESPACE } from '@kbn/securitysolution-list-constants';
+import { getSavedObjectType } from '@kbn/securitysolution-list-utils';
import { RuleParams } from '../../schemas/rule_schemas';
import { getSavedObjectNamePatternForExceptionsList } from './utils';
@@ -35,7 +35,7 @@ export const extractExceptionsList = ({
return exceptionsList.map((exceptionItem, index) => ({
name: getSavedObjectNamePatternForExceptionsList(index),
id: exceptionItem.id,
- type: EXCEPTION_LIST_NAMESPACE,
+ type: getSavedObjectType({ namespaceType: exceptionItem.namespace_type }),
}));
}
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts
index 31288559e9437..df5fdf4fcead4 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts
@@ -8,8 +8,11 @@
import { loggingSystemMock } from 'src/core/server/mocks';
import { extractReferences } from './extract_references';
import { RuleParams } from '../../schemas/rule_schemas';
-import { EXCEPTION_LIST_NAMESPACE } from '@kbn/securitysolution-list-constants';
-import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils';
+import {
+ EXCEPTION_LIST_NAMESPACE,
+ EXCEPTION_LIST_NAMESPACE_AGNOSTIC,
+} from '@kbn/securitysolution-list-constants';
+import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils/constants';
describe('extract_references', () => {
type FuncReturn = ReturnType;
@@ -43,6 +46,36 @@ describe('extract_references', () => {
{
id: '123',
name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_0`,
+ type: EXCEPTION_LIST_NAMESPACE_AGNOSTIC,
+ },
+ ],
+ });
+ });
+
+ test('It returns params untouched and the references extracted as 2 exception list saved object references', () => {
+ const params: Partial = {
+ note: 'some note',
+ exceptionsList: [
+ mockExceptionsList()[0],
+ { ...mockExceptionsList()[0], id: '456', namespace_type: 'single' },
+ ],
+ };
+ expect(
+ extractReferences({
+ logger,
+ params: params as RuleParams,
+ })
+ ).toEqual({
+ params: params as RuleParams,
+ references: [
+ {
+ id: '123',
+ name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_0`,
+ type: EXCEPTION_LIST_NAMESPACE_AGNOSTIC,
+ },
+ {
+ id: '456',
+ name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_1`,
type: EXCEPTION_LIST_NAMESPACE,
},
],
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts
index fc35088da66fc..72373eebf4fba 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts
@@ -7,10 +7,10 @@
import { loggingSystemMock } from 'src/core/server/mocks';
import { SavedObjectReference } from 'src/core/server';
-import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils';
import { EXCEPTION_LIST_NAMESPACE } from '@kbn/securitysolution-list-constants';
import { injectExceptionsReferences } from './inject_exceptions_list';
import { RuleParams } from '../../schemas/rule_schemas';
+import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils/constants';
describe('inject_exceptions_list', () => {
type FuncReturn = ReturnType;
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.test.ts
index a80f19ae011d7..eae4cb20f7948 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.test.ts
@@ -7,10 +7,10 @@
import { loggingSystemMock } from 'src/core/server/mocks';
import { SavedObjectReference } from 'src/core/server';
-import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils';
import { EXCEPTION_LIST_NAMESPACE } from '@kbn/securitysolution-list-constants';
import { injectReferences } from './inject_references';
import { RuleParams } from '../../schemas/rule_schemas';
+import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils/constants';
describe('inject_references', () => {
type FuncReturn = ReturnType;
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern.ts
index f4e33cf57fa2b..f0fd4e86e44b0 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern.ts
@@ -6,7 +6,7 @@
*/
/**
- * Given a name and index this will return the pattern of "${name_${index}"
+ * Given a name and index this will return the pattern of "${name}_${index}"
* @param name The name to suffix the string
* @param index The index to suffix the string
* @returns The pattern "${name_${index}"
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern_for_exception_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern_for_exception_list.test.ts
index 98c575e8835be..82bf2e1506f97 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern_for_exception_list.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern_for_exception_list.test.ts
@@ -5,10 +5,8 @@
* 2.0.
*/
-import {
- EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME,
- getSavedObjectNamePatternForExceptionsList,
-} from '.';
+import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './constants';
+import { getSavedObjectNamePatternForExceptionsList } from './get_saved_object_name_pattern_for_exception_list';
describe('get_saved_object_name_pattern_for_exception_list', () => {
test('returns expected pattern given a zero', () => {
diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.test.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.test.tsx
index 9ff90b50d0ad4..338d7d1809074 100644
--- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.test.tsx
+++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.test.tsx
@@ -21,10 +21,14 @@ jest.mock('./helpers');
describe('AddToCaseAction', () => {
const props = {
- ecsRowData: {
+ event: {
_id: 'test-id',
- _index: 'test-index',
- signal: { rule: { id: ['rule-id'], name: ['rule-name'], false_positives: [] } },
+ data: [],
+ ecs: {
+ _id: 'test-id',
+ _index: 'test-index',
+ signal: { rule: { id: ['rule-id'], name: ['rule-name'], false_positives: [] } },
+ },
},
casePermissions: {
crud: true,
@@ -94,18 +98,26 @@ describe('AddToCaseAction', () => {
@@ -129,16 +141,24 @@ describe('AddToCaseAction', () => {
diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.tsx
index 4b7e44112b24b..a292999ec75eb 100644
--- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.tsx
+++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.tsx
@@ -8,7 +8,7 @@
import React, { memo, useMemo, useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { CaseStatuses, StatusAll } from '../../../../../../cases/common';
-import { Ecs } from '../../../../../common/ecs';
+import { TimelineItem } from '../../../../../common/';
import { useAddToCase } from '../../../../hooks/use_add_to_case';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { TimelinesStartServices } from '../../../../types';
@@ -18,7 +18,7 @@ import * as i18n from './translations';
export interface AddToCaseActionProps {
ariaLabel?: string;
- ecsRowData: Ecs;
+ event?: TimelineItem;
useInsertTimeline?: Function;
casePermissions: {
crud: boolean;
@@ -30,15 +30,15 @@ export interface AddToCaseActionProps {
const AddToCaseActionComponent: React.FC = ({
ariaLabel = i18n.ACTION_ADD_TO_CASE_ARIA_LABEL,
- ecsRowData,
+ event,
useInsertTimeline,
casePermissions,
appId,
onClose,
}) => {
- const eventId = ecsRowData._id;
- const eventIndex = ecsRowData._index;
- const rule = ecsRowData.signal?.rule;
+ const eventId = event?.ecs._id ?? '';
+ const eventIndex = event?.ecs._index ?? '';
+ const rule = event?.ecs.signal?.rule;
const dispatch = useDispatch();
const { cases } = useKibana().services;
const {
@@ -49,7 +49,7 @@ const AddToCaseActionComponent: React.FC = ({
createCaseUrl,
isAllCaseModalOpen,
isCreateCaseFlyoutOpen,
- } = useAddToCase({ ecsRowData, useInsertTimeline, casePermissions, appId, onClose });
+ } = useAddToCase({ event, useInsertTimeline, casePermissions, appId, onClose });
const getAllCasesSelectorModalProps = useMemo(() => {
return {
@@ -66,6 +66,9 @@ const AddToCaseActionComponent: React.FC = ({
href: createCaseUrl,
onClick: goToCreateCase,
},
+ hooks: {
+ useInsertTimeline,
+ },
hiddenStatuses: [CaseStatuses.closed, StatusAll],
onRowClick: onCaseClicked,
updateCase: onCaseSuccess,
@@ -86,6 +89,7 @@ const AddToCaseActionComponent: React.FC = ({
rule?.name,
appId,
dispatch,
+ useInsertTimeline,
]);
const closeCaseFlyoutOpen = useCallback(() => {
diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action_button.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action_button.tsx
index 2ee57bb250928..28821028af3c7 100644
--- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action_button.tsx
+++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action_button.tsx
@@ -21,7 +21,7 @@ import * as i18n from './translations';
const AddToCaseActionButtonComponent: React.FC = ({
ariaLabel = i18n.ACTION_ADD_TO_CASE_ARIA_LABEL,
- ecsRowData,
+ event,
useInsertTimeline,
casePermissions,
appId,
@@ -36,7 +36,7 @@ const AddToCaseActionButtonComponent: React.FC = ({
openPopover,
closePopover,
isPopoverOpen,
- } = useAddToCase({ ecsRowData, useInsertTimeline, casePermissions, appId, onClose });
+ } = useAddToCase({ event, useInsertTimeline, casePermissions, appId, onClose });
const tooltipContext = userCanCrud
? isEventSupported
? i18n.ACTION_ADD_TO_CASE_TOOLTIP
diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_existing_case_button.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_existing_case_button.tsx
index 5f0fa5332b168..1a3c8267f946c 100644
--- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_existing_case_button.tsx
+++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_existing_case_button.tsx
@@ -14,14 +14,14 @@ import * as i18n from './translations';
const AddToCaseActionComponent: React.FC = ({
ariaLabel = i18n.ACTION_ADD_TO_CASE_ARIA_LABEL,
- ecsRowData,
+ event,
useInsertTimeline,
casePermissions,
appId,
onClose,
}) => {
const { addExistingCaseClick, isDisabled, userCanCrud } = useAddToCase({
- ecsRowData,
+ event,
useInsertTimeline,
casePermissions,
appId,
diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx
index 4cf81aaf40cdb..7585382b14820 100644
--- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx
+++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx
@@ -14,14 +14,14 @@ import * as i18n from './translations';
const AddToCaseActionComponent: React.FC = ({
ariaLabel = i18n.ACTION_ADD_TO_CASE_ARIA_LABEL,
- ecsRowData,
+ event,
useInsertTimeline,
casePermissions,
appId,
onClose,
}) => {
const { addNewCaseClick, isDisabled, userCanCrud } = useAddToCase({
- ecsRowData,
+ event,
useInsertTimeline,
casePermissions,
appId,
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx
index 80d413a29e6fc..b3ff8c9533cbe 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx
@@ -6,7 +6,7 @@
*/
import React, { useCallback, useEffect, useMemo } from 'react';
-import { EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
+import { EuiContextMenuItem, EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { DraggableId } from 'react-beautiful-dnd';
import { useDispatch } from 'react-redux';
@@ -45,7 +45,7 @@ const useGetHandleStartDragToTimeline = ({
export interface AddToTimelineButtonProps extends HoverActionComponentProps {
/** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */
- Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon;
+ Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon | typeof EuiContextMenuItem;
draggableId?: DraggableId;
dataProvider?: DataProvider[] | DataProvider;
}
@@ -53,13 +53,13 @@ export interface AddToTimelineButtonProps extends HoverActionComponentProps {
const AddToTimelineButton: React.FC = React.memo(
({
Component,
- closePopOver,
dataProvider,
defaultFocusedButtonRef,
draggableId,
field,
keyboardEvent,
ownFocus,
+ onClick,
showTooltip = false,
value,
}) => {
@@ -84,10 +84,10 @@ const AddToTimelineButton: React.FC = React.memo(
});
}
- if (closePopOver != null) {
- closePopOver();
+ if (onClick != null) {
+ onClick();
}
- }, [addSuccess, closePopOver, dataProvider, dispatch, draggableId, startDragToTimeline]);
+ }, [addSuccess, onClick, dataProvider, dispatch, draggableId, startDragToTimeline]);
useEffect(() => {
if (!ownFocus) {
@@ -106,6 +106,7 @@ const AddToTimelineButton: React.FC = React.memo(
aria-label={i18n.ADD_TO_TIMELINE}
buttonRef={defaultFocusedButtonRef}
data-test-subj="add-to-timeline"
+ icon="timeline"
iconType="timeline"
onClick={handleStartDragToTimeline}
title={i18n.ADD_TO_TIMELINE}
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/column_toggle.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/column_toggle.tsx
index d59383b8553ea..c7936c6b17aaf 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/actions/column_toggle.tsx
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/column_toggle.tsx
@@ -5,8 +5,8 @@
* 2.0.
*/
-import React, { useCallback, useEffect } from 'react';
-import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
+import React, { useCallback, useEffect, useMemo } from 'react';
+import { EuiContextMenuItem, EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { stopPropagationAndPreventDefault } from '../../../../common';
@@ -33,6 +33,7 @@ export const NESTED_COLUMN = (field: string) =>
export const COLUMN_TOGGLE_KEYBOARD_SHORTCUT = 'i';
export interface ColumnToggleProps extends HoverActionComponentProps {
+ Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon | typeof EuiContextMenuItem;
isDisabled: boolean;
isObjectArray: boolean;
toggleColumn: (column: ColumnHeaderOptions) => void;
@@ -40,13 +41,14 @@ export interface ColumnToggleProps extends HoverActionComponentProps {
const ColumnToggleButton: React.FC = React.memo(
({
- closePopOver,
+ Component,
defaultFocusedButtonRef,
field,
isDisabled,
isObjectArray,
keyboardEvent,
ownFocus,
+ onClick,
showTooltip = false,
toggleColumn,
value,
@@ -59,10 +61,10 @@ const ColumnToggleButton: React.FC = React.memo(
id: field,
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
});
- if (closePopOver != null) {
- closePopOver();
+ if (onClick != null) {
+ onClick();
}
- }, [closePopOver, field, toggleColumn]);
+ }, [onClick, field, toggleColumn]);
useEffect(() => {
if (!ownFocus) {
@@ -74,6 +76,36 @@ const ColumnToggleButton: React.FC = React.memo(
}
}, [handleToggleColumn, keyboardEvent, ownFocus]);
+ const button = useMemo(
+ () =>
+ Component ? (
+
+ {label}
+
+ ) : (
+
+ ),
+ [Component, defaultFocusedButtonRef, field, handleToggleColumn, isDisabled, label]
+ );
+
return showTooltip ? (
= React.memo(
/>
}
>
-
+ {button}
) : (
-
+ button
);
}
);
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx
index c188af67d33fd..1c7fe5c82df85 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui';
+import { EuiContextMenuItem, EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui';
import copy from 'copy-to-clipboard';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { i18n } from '@kbn/i18n';
@@ -26,12 +26,12 @@ export const COPY_TO_CLIPBOARD_KEYBOARD_SHORTCUT = 'c';
export interface CopyProps extends HoverActionComponentProps {
/** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */
- Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon;
+ Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon | typeof EuiContextMenuItem;
isHoverAction?: boolean;
}
const CopyButton: React.FC = React.memo(
- ({ Component, closePopOver, field, isHoverAction, keyboardEvent, ownFocus, value }) => {
+ ({ Component, field, isHoverAction, onClick, keyboardEvent, ownFocus, value }) => {
const { addSuccess } = useAppToasts();
const panelRef = useRef(null);
useEffect(() => {
@@ -46,28 +46,32 @@ const CopyButton: React.FC = React.memo(
if (copyToClipboardButton != null) {
copyToClipboardButton.click();
}
- if (closePopOver != null) {
- closePopOver();
+ if (onClick != null) {
+ onClick();
}
}
- }, [closePopOver, keyboardEvent, ownFocus]);
+ }, [onClick, keyboardEvent, ownFocus]);
const text = useMemo(() => `${field}${value != null ? `: "${value}"` : ''}`, [field, value]);
- const onClick = useCallback(() => {
+ const handleOnClick = useCallback(() => {
const isSuccess = copy(text, { debug: true });
+ if (onClick != null) {
+ onClick();
+ }
if (isSuccess) {
addSuccess(SUCCESS_TOAST_TITLE(field), { toastLifeTimeMs: 800 });
}
- }, [addSuccess, field, text]);
+ }, [addSuccess, field, onClick, text]);
return Component ? (
{COPY_TO_CLIPBOARD}
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_for_value.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_for_value.tsx
index 6ba55f50560eb..549c2da0d7672 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_for_value.tsx
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_for_value.tsx
@@ -24,13 +24,13 @@ export type FilterForValueProps = HoverActionComponentProps & FilterValueFnArgs;
const FilterForValueButton: React.FC = React.memo(
({
Component,
- closePopOver,
defaultFocusedButtonRef,
field,
filterManager,
keyboardEvent,
onFilterAdded,
ownFocus,
+ onClick,
showTooltip = false,
value,
}) => {
@@ -49,10 +49,11 @@ const FilterForValueButton: React.FC = React.memo(
onFilterAdded();
}
}
- if (closePopOver != null) {
- closePopOver();
+
+ if (onClick != null) {
+ onClick();
}
- }, [closePopOver, field, filterManager, onFilterAdded, value]);
+ }, [field, filterManager, onClick, onFilterAdded, value]);
useEffect(() => {
if (!ownFocus) {
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_out_value.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_out_value.tsx
index c19f4febf49d9..51f2943ff051e 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_out_value.tsx
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/filter_out_value.tsx
@@ -23,13 +23,13 @@ export const FILTER_OUT_VALUE_KEYBOARD_SHORTCUT = 'o';
const FilterOutValueButton: React.FC = React.memo(
({
Component,
- closePopOver,
defaultFocusedButtonRef,
field,
filterManager,
keyboardEvent,
onFilterAdded,
ownFocus,
+ onClick,
showTooltip = false,
value,
}) => {
@@ -50,10 +50,10 @@ const FilterOutValueButton: React.FC {
if (!ownFocus) {
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/overflow.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/overflow.tsx
new file mode 100644
index 0000000000000..a02257d72530e
--- /dev/null
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/overflow.tsx
@@ -0,0 +1,135 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useEffect, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import {
+ EuiButtonIcon,
+ EuiButtonEmpty,
+ EuiContextMenuPanel,
+ EuiContextMenuItem,
+ EuiPopover,
+ EuiToolTip,
+} from '@elastic/eui';
+
+import { stopPropagationAndPreventDefault } from '../../../../common';
+import { TooltipWithKeyboardShortcut } from '../../tooltip_with_keyboard_shortcut';
+import { getAdditionalScreenReaderOnlyContext } from '../utils';
+import { HoverActionComponentProps } from './types';
+
+export const MORE_ACTIONS = i18n.translate('xpack.timelines.hoverActions.moreActions', {
+ defaultMessage: 'More actions',
+});
+
+export const FILTER_OUT_VALUE_KEYBOARD_SHORTCUT = 'm';
+
+export interface OverflowButtonProps extends HoverActionComponentProps {
+ closePopOver: () => void;
+ Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon | typeof EuiContextMenuItem;
+ items: JSX.Element[];
+ isOverflowPopoverOpen: boolean;
+}
+
+const OverflowButton: React.FC = React.memo(
+ ({
+ closePopOver,
+ Component,
+ defaultFocusedButtonRef,
+ field,
+ items,
+ isOverflowPopoverOpen,
+ keyboardEvent,
+ ownFocus,
+ onClick,
+ showTooltip = false,
+ value,
+ }) => {
+ useEffect(() => {
+ if (!ownFocus) {
+ return;
+ }
+ if (keyboardEvent?.key === FILTER_OUT_VALUE_KEYBOARD_SHORTCUT) {
+ stopPropagationAndPreventDefault(keyboardEvent);
+ if (onClick != null) {
+ onClick();
+ }
+ }
+ }, [keyboardEvent, onClick, ownFocus]);
+
+ const popover = useMemo(
+ () => (
+
+ {MORE_ACTIONS}
+
+ ) : (
+
+ )
+ }
+ isOpen={isOverflowPopoverOpen}
+ closePopover={closePopOver}
+ panelPaddingSize="none"
+ anchorPosition="downLeft"
+ >
+
+
+ ),
+ [
+ Component,
+ defaultFocusedButtonRef,
+ field,
+ onClick,
+ isOverflowPopoverOpen,
+ closePopOver,
+ items,
+ ]
+ );
+
+ return showTooltip ? (
+
+ }
+ >
+ {popover}
+
+ ) : (
+ popover
+ );
+ }
+);
+
+OverflowButton.displayName = 'OverflowButton';
+
+// eslint-disable-next-line import/no-default-export
+export { OverflowButton as default };
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/types.ts b/x-pack/plugins/timelines/public/components/hover_actions/actions/types.ts
index 06069ede15b98..5be8fd8904f1a 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/actions/types.ts
+++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/types.ts
@@ -18,11 +18,11 @@ export interface FilterValueFnArgs {
}
export interface HoverActionComponentProps {
- closePopOver?: () => void;
defaultFocusedButtonRef?: EuiButtonIconPropsForButton['buttonRef'];
field: string;
keyboardEvent?: React.KeyboardEvent;
ownFocus: boolean;
+ onClick?: () => void;
showTooltip?: boolean;
value?: string[] | string | null;
}
diff --git a/x-pack/plugins/timelines/public/components/hover_actions/index.tsx b/x-pack/plugins/timelines/public/components/hover_actions/index.tsx
index fc8fcfa488a76..02ebcb8abe912 100644
--- a/x-pack/plugins/timelines/public/components/hover_actions/index.tsx
+++ b/x-pack/plugins/timelines/public/components/hover_actions/index.tsx
@@ -13,6 +13,7 @@ import type { AddToTimelineButtonProps } from './actions/add_to_timeline';
import type { ColumnToggleProps } from './actions/column_toggle';
import type { CopyProps } from './actions/copy';
import type { HoverActionComponentProps, FilterValueFnArgs } from './actions/types';
+import type { OverflowButtonProps } from './actions/overflow';
export interface HoverActionsConfig {
getAddToTimelineButton: (
@@ -26,6 +27,7 @@ export interface HoverActionsConfig {
getFilterOutValueButton: (
props: HoverActionComponentProps & FilterValueFnArgs
) => ReactElement;
+ getOverflowButton: (props: OverflowButtonProps) => ReactElement;
}
const AddToTimelineButtonLazy = React.lazy(() => import('./actions/add_to_timeline'));
@@ -77,10 +79,20 @@ const getFilterOutValueButtonLazy = (props: HoverActionComponentProps & FilterVa
);
};
+const OverflowButtonLazy = React.lazy(() => import('./actions/overflow'));
+const getOverflowButtonLazy = (props: OverflowButtonProps) => {
+ return (
+ }>
+
+
+ );
+};
+
export const getHoverActions = (store?: Store): HoverActionsConfig => ({
getAddToTimelineButton: getAddToTimelineButtonLazy.bind(null, store!),
getColumnToggleButton: getColumnToggleButtonLazy,
getCopyButton: getCopyButtonLazy,
getFilterForValueButton: getFilterForValueButtonLazy,
getFilterOutValueButton: getFilterOutValueButtonLazy,
+ getOverflowButton: getOverflowButtonLazy,
});
diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx
index e9051b72db5e5..57f285542ed7a 100644
--- a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx
+++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx
@@ -332,7 +332,7 @@ export const BodyComponent = React.memo(
() => ({
additionalControls: (
<>
- {alertCountText}
+ {alertCountText}
{showBulkActions ? (
<>
}>
@@ -507,15 +507,19 @@ export const BodyComponent = React.memo(
[browserFields, columnHeaders, data, defaultCellActions]
);
- const renderTGridCellValue: (
- x: EuiDataGridCellValueElementProps
- ) => React.ReactNode = useCallback(
- ({ columnId, rowIndex, setCellProps }) => {
+ const renderTGridCellValue = useMemo(() => {
+ const Cell: React.FC = ({
+ columnId,
+ rowIndex,
+ setCellProps,
+ }): React.ReactElement | null => {
const rowData = rowIndex < data.length ? data[rowIndex].data : null;
const header = columnHeaders.find((h) => h.id === columnId);
const eventId = rowIndex < data.length ? data[rowIndex]._id : null;
- addBuildingBlockStyle(data[rowIndex].ecs, theme, setCellProps);
+ useEffect(() => {
+ addBuildingBlockStyle(data[rowIndex].ecs, theme, setCellProps);
+ }, [rowIndex, setCellProps]);
if (rowData == null || header == null || eventId == null) {
return null;
@@ -537,10 +541,10 @@ export const BodyComponent = React.memo(
ecsData: data[rowIndex].ecs,
browserFields,
rowRenderers,
- });
- },
- [columnHeaders, data, id, renderCellValue, tabType, theme, browserFields, rowRenderers]
- );
+ }) as React.ReactElement;
+ };
+ return Cell;
+ }, [columnHeaders, data, id, renderCellValue, tabType, theme, browserFields, rowRenderers]);
return (
`
display: flex;
flex-direction: column;
+ position: relative;
${({ $isFullScreen }) =>
$isFullScreen &&
@@ -119,6 +122,7 @@ export interface TGridIntegratedProps {
deletedEventIds: Readonly;
docValueFields: DocValueFields[];
end: string;
+ entityType: EntityType;
filters: Filter[];
globalFullScreen: boolean;
headerFilterGroup?: React.ReactNode;
@@ -155,6 +159,7 @@ const TGridIntegratedComponent: React.FC = ({
deletedEventIds,
docValueFields,
end,
+ entityType,
filters,
globalFullScreen,
headerFilterGroup,
@@ -250,6 +255,7 @@ const TGridIntegratedComponent: React.FC = ({
] = useTimelineEvents({
alertConsumers: SECURITY_ALERTS_CONSUMERS,
docValueFields,
+ entityType,
fields,
filterQuery: combinedQueries!.filterQuery,
id,
@@ -307,6 +313,8 @@ const TGridIntegratedComponent: React.FC = ({
return (
+ {loading && }
+
{canQueryTimeline ? (
<>
= ({
-
-
+ {nonDeletedEvents.length === 0 && loading === false ? (
+
+
+
+ }
+ titleSize="s"
+ body={
+
+
+
+ }
+ />
+ ) : (
+ <>
+
+
+ >
+ )}
diff --git a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx
index 9fb6af6199a4c..2890c83770792 100644
--- a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx
+++ b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx
@@ -9,10 +9,9 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import { isEmpty } from 'lodash/fp';
import React, { useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
-import { useDispatch } from 'react-redux';
-
+import { useDispatch, useSelector } from 'react-redux';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
-import { Direction } from '../../../../common/search_strategy';
+import { Direction, EntityType } from '../../../../common/search_strategy';
import type { CoreStart } from '../../../../../../../src/core/public';
import { TGridCellAction, TimelineTabs } from '../../../../common/types/timeline';
import type {
@@ -40,6 +39,7 @@ import {
resolverIsShowing,
} from '../helpers';
import { tGridActions, tGridSelectors } from '../../../store/t_grid';
+import type { State } from '../../../store/t_grid';
import { useTimelineEvents } from '../../../container';
import { HeaderSection } from '../header_section';
import { StatefulBody } from '../body';
@@ -49,6 +49,7 @@ import { SELECTOR_TIMELINE_GLOBAL_CONTAINER, UpdatedFlexItem } from '../styles';
import * as i18n from '../translations';
import { InspectButtonContainer } from '../../inspect';
import { useFetchIndex } from '../../../container/source';
+import { AddToCaseAction } from '../../actions/timeline/cases/add_to_case_action';
export const EVENTS_VIEWER_HEADER_HEIGHT = 90; // px
const COMPACT_HEADER_HEIGHT = 36; // px
@@ -104,10 +105,17 @@ const HeaderFilterGroupWrapper = styled.header<{ show: boolean }>`
export interface TGridStandaloneProps {
alertConsumers: AlertConsumers[];
+ appId: string;
+ casePermissions: {
+ crud: boolean;
+ read: boolean;
+ } | null;
+ afterCaseSelection?: Function;
columns: ColumnHeaderOptions[];
defaultCellActions?: TGridCellAction[];
deletedEventIds: Readonly;
end: string;
+ entityType?: EntityType;
loadingText: React.ReactNode;
filters: Filter[];
footerText: React.ReactNode;
@@ -134,11 +142,15 @@ export interface TGridStandaloneProps {
const basicUnit = (n: number) => i18n.UNIT(n);
const TGridStandaloneComponent: React.FC = ({
+ afterCaseSelection,
alertConsumers,
+ appId,
+ casePermissions,
columns,
defaultCellActions,
deletedEventIds,
end,
+ entityType = 'alerts',
loadingText,
filters,
footerText,
@@ -227,6 +239,7 @@ const TGridStandaloneComponent: React.FC = ({
] = useTimelineEvents({
alertConsumers,
docValueFields: [],
+ entityType,
excludeEcsData: true,
fields,
filterQuery: combinedQueries!.filterQuery,
@@ -245,6 +258,24 @@ const TGridStandaloneComponent: React.FC = ({
() => (totalCount > 0 ? totalCount - deletedEventIds.length : 0),
[deletedEventIds.length, totalCount]
);
+ const activeCaseFlowId = useSelector((state: State) => tGridSelectors.activeCaseFlowId(state));
+ const selectedEvent = useMemo(() => {
+ const matchedEvent = events.find((event) => event.ecs._id === activeCaseFlowId);
+ if (matchedEvent) {
+ return matchedEvent;
+ } else {
+ return undefined;
+ }
+ }, [events, activeCaseFlowId]);
+
+ const addToCaseActionProps = useMemo(() => {
+ return {
+ event: selectedEvent,
+ casePermissions: casePermissions ?? null,
+ appId,
+ onClose: afterCaseSelection,
+ };
+ }, [appId, casePermissions, afterCaseSelection, selectedEvent]);
const nonDeletedEvents = useMemo(() => events.filter((e) => !deletedEventIds.includes(e._id)), [
deletedEventIds,
@@ -384,6 +415,7 @@ const TGridStandaloneComponent: React.FC = ({
>
) : null}
+
);
diff --git a/x-pack/plugins/timelines/public/container/index.tsx b/x-pack/plugins/timelines/public/container/index.tsx
index d3e58889a3d85..81578a001f6a4 100644
--- a/x-pack/plugins/timelines/public/container/index.tsx
+++ b/x-pack/plugins/timelines/public/container/index.tsx
@@ -22,6 +22,7 @@ import {
Direction,
TimelineFactoryQueryTypes,
TimelineEventsQueries,
+ EntityType,
} from '../../common/search_strategy';
import type {
DocValueFields,
@@ -71,6 +72,7 @@ export interface UseTimelineEventsProps {
filterQuery?: ESQuery | string;
skip?: boolean;
endDate: string;
+ entityType: EntityType;
excludeEcsData?: boolean;
id: string;
fields: string[];
@@ -113,6 +115,7 @@ export const useTimelineEvents = ({
alertConsumers = NO_CONSUMERS,
docValueFields,
endDate,
+ entityType,
excludeEcsData = false,
id = ID,
indexNames,
@@ -197,7 +200,7 @@ export const useTimelineEvents = ({
if (data && data.search) {
searchSubscription$.current = data.search
.search, TimelineResponse>(
- { ...request, entityType: 'alerts' },
+ { ...request, entityType },
{
strategy:
request.language === 'eql'
@@ -209,7 +212,6 @@ export const useTimelineEvents = ({
.subscribe({
next: (response) => {
if (isCompleteResponse(response)) {
- setLoading(false);
setTimelineResponse((prevResponse) => {
const newTimelineResponse = {
...prevResponse,
@@ -222,6 +224,8 @@ export const useTimelineEvents = ({
setUpdated(newTimelineResponse.updatedAt);
return newTimelineResponse;
});
+ setLoading(false);
+
searchSubscription$.current.unsubscribe();
} else if (isErrorResponse(response)) {
setLoading(false);
@@ -245,7 +249,7 @@ export const useTimelineEvents = ({
asyncSearch();
refetch.current = asyncSearch;
},
- [skip, data, setUpdated, addWarning, addError]
+ [skip, data, entityType, setUpdated, addWarning, addError]
);
useEffect(() => {
diff --git a/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts b/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts
index a03c1cc8f1c9a..83a0ef1fa4dc8 100644
--- a/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts
+++ b/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts
@@ -75,15 +75,15 @@ interface PostCommentArg {
}
export const useAddToCase = ({
- ecsRowData,
+ event,
useInsertTimeline,
casePermissions,
appId,
onClose,
}: AddToCaseActionProps): UseAddToCase => {
- const eventId = ecsRowData._id;
- const eventIndex = ecsRowData._index;
- const rule = ecsRowData.signal?.rule;
+ const eventId = event?.ecs._id ?? '';
+ const eventIndex = event?.ecs._index ?? '';
+ const rule = event?.ecs.signal?.rule;
const dispatch = useDispatch();
// TODO: use correct value in standalone or integrated.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -109,25 +109,36 @@ export const useAddToCase = ({
}
}, [timelineById]);
const {
- application: { navigateToApp, getUrlForApp },
+ application: { navigateToApp, getUrlForApp, navigateToUrl },
notifications: { toasts },
} = useKibana().services;
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const openPopover = useCallback(() => setIsPopoverOpen(true), []);
const closePopover = useCallback(() => setIsPopoverOpen(false), []);
- const isEventSupported = !isEmpty(ecsRowData.signal?.rule?.id);
+ const isAlert = useMemo(() => {
+ if (event !== undefined) {
+ const data = [...event.data];
+ return data.some(({ field }) => field === 'kibana.alert.uuid');
+ } else {
+ return false;
+ }
+ }, [event]);
+ const isSecurityAlert = useMemo(() => {
+ return !isEmpty(event?.ecs.signal?.rule?.id);
+ }, [event]);
+ const isEventSupported = isSecurityAlert || isAlert;
const userCanCrud = casePermissions?.crud ?? false;
const isDisabled = !userCanCrud || !isEventSupported;
const onViewCaseClick = useCallback(
(id) => {
- navigateToApp(appId, {
- deepLinkId: appId === 'securitySolution' ? 'case' : 'cases',
- path: getCaseDetailsUrl({ id }),
- });
+ const caseDetailsUrl = getCaseDetailsUrl({ id });
+ const appUrl = getUrlForApp(appId);
+ const fullCaseUrl = `${appUrl}/cases/${caseDetailsUrl}`;
+ navigateToUrl(fullCaseUrl);
},
- [navigateToApp, appId]
+ [navigateToUrl, appId, getUrlForApp]
);
const currentSearch = useLocation().search;
const urlSearch = useMemo(() => currentSearch, [currentSearch]);
diff --git a/x-pack/plugins/timelines/public/index.ts b/x-pack/plugins/timelines/public/index.ts
index d31e80de7d285..c8b14b4018765 100644
--- a/x-pack/plugins/timelines/public/index.ts
+++ b/x-pack/plugins/timelines/public/index.ts
@@ -22,6 +22,7 @@ export type {
export { Direction } from '../common/search_strategy/common';
export { tGridReducer } from './store/t_grid/reducer';
export type { TGridModelForTimeline, TimelineState, TimelinesUIStart } from './types';
+export { TGridType, SortDirection } from './types';
export {
ARIA_COLINDEX_ATTRIBUTE,
ARIA_ROWINDEX_ATTRIBUTE,
diff --git a/x-pack/plugins/timelines/public/methods/index.tsx b/x-pack/plugins/timelines/public/methods/index.tsx
index dca72a590ea30..55d15a6410fde 100644
--- a/x-pack/plugins/timelines/public/methods/index.tsx
+++ b/x-pack/plugins/timelines/public/methods/index.tsx
@@ -15,6 +15,24 @@ import type { DataPublicPluginStart } from '../../../../../src/plugins/data/publ
import type { TGridProps } from '../types';
import type { LastUpdatedAtProps, LoadingPanelProps, FieldBrowserProps } from '../components';
import type { AddToCaseActionProps } from '../components/actions/timeline/cases/add_to_case_action';
+import { initialTGridState } from '../store/t_grid/reducer';
+import { createStore } from '../store/t_grid';
+
+const initializeStore = ({
+ store,
+ storage,
+ setStore,
+}: {
+ store?: Store;
+ storage: Storage;
+ setStore: (store: Store) => void;
+}) => {
+ let tGridStore = store;
+ if (!tGridStore) {
+ tGridStore = createStore(initialTGridState, storage);
+ setStore(tGridStore);
+ }
+};
const TimelineLazy = lazy(() => import('../components'));
export const getTGridLazy = (
@@ -31,6 +49,7 @@ export const getTGridLazy = (
setStore: (store: Store) => void;
}
) => {
+ initializeStore({ store, storage, setStore });
return (
}>
@@ -66,7 +85,10 @@ export const getFieldsBrowserLazy = (props: FieldBrowserProps, { store }: { stor
};
const AddToCaseLazy = lazy(() => import('../components/actions/timeline/cases/add_to_case_action'));
-export const getAddToCaseLazy = (props: AddToCaseActionProps, store: Store) => {
+export const getAddToCaseLazy = (
+ props: AddToCaseActionProps,
+ { store, storage, setStore }: { store: Store; storage: Storage; setStore: (store: Store) => void }
+) => {
return (
}>
@@ -81,7 +103,11 @@ export const getAddToCaseLazy = (props: AddToCaseActionProps, store: Store) => {
const AddToCasePopover = lazy(
() => import('../components/actions/timeline/cases/add_to_case_action_button')
);
-export const getAddToCasePopoverLazy = (props: AddToCaseActionProps, store: Store) => {
+export const getAddToCasePopoverLazy = (
+ props: AddToCaseActionProps,
+ { store, storage, setStore }: { store: Store; storage: Storage; setStore: (store: Store) => void }
+) => {
+ initializeStore({ store, storage, setStore });
return (
}>
@@ -96,7 +122,11 @@ export const getAddToCasePopoverLazy = (props: AddToCaseActionProps, store: Stor
const AddToExistingButton = lazy(
() => import('../components/actions/timeline/cases/add_to_existing_case_button')
);
-export const getAddToExistingCaseButtonLazy = (props: AddToCaseActionProps, store: Store) => {
+export const getAddToExistingCaseButtonLazy = (
+ props: AddToCaseActionProps,
+ { store, storage, setStore }: { store: Store; storage: Storage; setStore: (store: Store) => void }
+) => {
+ initializeStore({ store, storage, setStore });
return (
}>
@@ -111,7 +141,11 @@ export const getAddToExistingCaseButtonLazy = (props: AddToCaseActionProps, stor
const AddToNewCaseButton = lazy(
() => import('../components/actions/timeline/cases/add_to_new_case_button')
);
-export const getAddToNewCaseButtonLazy = (props: AddToCaseActionProps, store: Store) => {
+export const getAddToNewCaseButtonLazy = (
+ props: AddToCaseActionProps,
+ { store, storage, setStore }: { store: Store; storage: Storage; setStore: (store: Store) => void }
+) => {
+ initializeStore({ store, storage, setStore });
return (
}>
diff --git a/x-pack/plugins/timelines/public/mock/mock_hover_actions.tsx b/x-pack/plugins/timelines/public/mock/mock_hover_actions.tsx
index 5a8afb2036abf..0a8542713da68 100644
--- a/x-pack/plugins/timelines/public/mock/mock_hover_actions.tsx
+++ b/x-pack/plugins/timelines/public/mock/mock_hover_actions.tsx
@@ -13,4 +13,9 @@ export const mockHoverActions = {
getCopyButton: () => <>{'Copy button'}>,
getFilterForValueButton: () => <>{'Filter button'}>,
getFilterOutValueButton: () => <>{'Filter out button'}>,
+ getOverflowButton: (props: { field: string }) => (
+
+ {'Overflow button'}
+
+ ),
};
diff --git a/x-pack/plugins/timelines/public/plugin.ts b/x-pack/plugins/timelines/public/plugin.ts
index fb1dd360d074f..74e1f2b32844a 100644
--- a/x-pack/plugins/timelines/public/plugin.ts
+++ b/x-pack/plugins/timelines/public/plugin.ts
@@ -82,16 +82,32 @@ export class TimelinesPlugin implements Plugin {
this.setStore(store);
},
getAddToCaseAction: (props) => {
- return getAddToCaseLazy(props, this._store!);
+ return getAddToCaseLazy(props, {
+ store: this._store!,
+ storage: this._storage,
+ setStore: this.setStore.bind(this),
+ });
},
getAddToCasePopover: (props) => {
- return getAddToCasePopoverLazy(props, this._store!);
+ return getAddToCasePopoverLazy(props, {
+ store: this._store!,
+ storage: this._storage,
+ setStore: this.setStore.bind(this),
+ });
},
getAddToExistingCaseButton: (props) => {
- return getAddToExistingCaseButtonLazy(props, this._store!);
+ return getAddToExistingCaseButtonLazy(props, {
+ store: this._store!,
+ storage: this._storage,
+ setStore: this.setStore.bind(this),
+ });
},
getAddToNewCaseButton: (props) => {
- return getAddToNewCaseButtonLazy(props, this._store!);
+ return getAddToNewCaseButtonLazy(props, {
+ store: this._store!,
+ storage: this._storage,
+ setStore: this.setStore.bind(this),
+ });
},
};
}
diff --git a/x-pack/plugins/timelines/public/store/t_grid/selectors.ts b/x-pack/plugins/timelines/public/store/t_grid/selectors.ts
index 710a842d4563a..dff78a691b88f 100644
--- a/x-pack/plugins/timelines/public/store/t_grid/selectors.ts
+++ b/x-pack/plugins/timelines/public/store/t_grid/selectors.ts
@@ -6,11 +6,26 @@
*/
import { getOr } from 'lodash/fp';
import { createSelector } from 'reselect';
-import { TGridModel } from '.';
+import { TGridModel, State } from '.';
import { tGridDefaults, getTGridManageDefaults } from './defaults';
+interface TGridById {
+ [id: string]: TGridModel;
+}
+
const getDefaultTgrid = (id: string) => ({ ...tGridDefaults, ...getTGridManageDefaults(id) });
+const standaloneTGridById = (state: State): TGridById => state.timelineById;
+
+export const activeCaseFlowId = createSelector(standaloneTGridById, (tGrid) => {
+ return (
+ tGrid &&
+ Object.entries(tGrid)
+ .map(([id, data]) => (data.isAddToExistingCaseOpen || data.isCreateNewCaseOpen ? id : null))
+ .find((id) => id)
+ );
+});
+
export const selectTGridById = (state: unknown, timelineId: string): TGridModel => {
return getOr(
getOr(getDefaultTgrid(timelineId), ['timelineById', timelineId], state),
diff --git a/x-pack/plugins/timelines/public/types.ts b/x-pack/plugins/timelines/public/types.ts
index 4d6165a9e38a7..2b5abbcc36a94 100644
--- a/x-pack/plugins/timelines/public/types.ts
+++ b/x-pack/plugins/timelines/public/types.ts
@@ -18,6 +18,7 @@ import type {
UseDraggableKeyboardWrapper,
UseDraggableKeyboardWrapperProps,
} from './components';
+export type { SortDirection } from '../common';
import type { TGridIntegratedProps } from './components/t_grid/integrated';
import type { TGridStandaloneProps } from './components/t_grid/standalone';
import type { UseAddToTimelineProps, UseAddToTimeline } from './hooks/use_add_to_timeline';
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index b45a321d56cf8..6160f22675cb9 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -18317,7 +18317,6 @@
"xpack.observability.alertsFlyout.statusLabel": "ステータス",
"xpack.observability.alertsFlyout.triggeredLabel": "実行済み",
"xpack.observability.alertsLinkTitle": "アラート",
- "xpack.observability.alertsTable.viewInAppButtonLabel": "アプリで表示",
"xpack.observability.alertsTitle": "アラート",
"xpack.observability.breadcrumbs.alertsLinkText": "アラート",
"xpack.observability.breadcrumbs.casesConfigureLinkText": "構成",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index d4d8adf6ed5ea..0a65035a585e5 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -18728,7 +18728,6 @@
"xpack.observability.alertsFlyout.statusLabel": "状态",
"xpack.observability.alertsFlyout.triggeredLabel": "已触发",
"xpack.observability.alertsLinkTitle": "告警",
- "xpack.observability.alertsTable.viewInAppButtonLabel": "在应用中查看",
"xpack.observability.alertsTitle": "告警",
"xpack.observability.breadcrumbs.alertsLinkText": "告警",
"xpack.observability.breadcrumbs.casesConfigureLinkText": "配置",
diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts
index eedc5566ba1d5..b50a7efc9d907 100644
--- a/x-pack/plugins/triggers_actions_ui/public/index.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/index.ts
@@ -21,6 +21,7 @@ export type {
IErrorObject,
AlertFlyoutCloseReason,
AlertTypeParams,
+ AsApiContract,
} from './types';
export {
diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts
index b17d0493c4494..f01967592ea8c 100644
--- a/x-pack/plugins/triggers_actions_ui/public/types.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/types.ts
@@ -17,6 +17,7 @@ import {
AlertHistoryDocumentTemplate,
ALERT_HISTORY_PREFIX,
AlertHistoryDefaultIndexName,
+ AsApiContract,
} from '../../actions/common';
import { TypeRegistry } from './application/type_registry';
import {
@@ -58,6 +59,7 @@ export {
AlertHistoryDocumentTemplate,
AlertHistoryDefaultIndexName,
ALERT_HISTORY_PREFIX,
+ AsApiContract,
};
export type ActionTypeIndex = Record;
diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/cluster.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/cluster.test.ts
index 412ce348d56e3..6cbaf1cf9dc41 100644
--- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/cluster.test.ts
+++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/cluster.test.ts
@@ -6,7 +6,7 @@
*/
import { act } from 'react-dom/test-utils';
-import { MlAction, UpgradeAssistantStatus } from '../../common/types';
+import { MlAction, ESUpgradeStatus } from '../../common/types';
import { ClusterTestBed, setupClusterPage, setupEnvironment } from './helpers';
@@ -21,8 +21,8 @@ describe('Cluster tab', () => {
describe('with deprecations', () => {
const snapshotId = '1';
const jobId = 'deprecation_check_job';
- const upgradeStatusMockResponse: UpgradeAssistantStatus = {
- readyForUpgrade: false,
+ const esDeprecationsMockResponse: ESUpgradeStatus = {
+ totalCriticalDeprecations: 1,
cluster: [
{
level: 'critical',
@@ -42,7 +42,7 @@ describe('Cluster tab', () => {
};
beforeEach(async () => {
- httpRequestsMockHelpers.setLoadEsDeprecationsResponse(upgradeStatusMockResponse);
+ httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse);
httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({ isEnabled: true });
await act(async () => {
@@ -79,7 +79,7 @@ describe('Cluster tab', () => {
actions.clickExpandAll();
// The data-test-subj is derived from the deprecation message
- const accordionTestSubj = `depgroup_${upgradeStatusMockResponse.cluster[0].message
+ const accordionTestSubj = `depgroup_${esDeprecationsMockResponse.cluster[0].message
.split(' ')
.join('_')}`;
@@ -155,7 +155,7 @@ describe('Cluster tab', () => {
expect(upgradeRequest.method).toBe('POST');
expect(upgradeRequest.url).toBe('/api/upgrade_assistant/ml_snapshots');
- const accordionTestSubj = `depgroup_${upgradeStatusMockResponse.cluster[0].message
+ const accordionTestSubj = `depgroup_${esDeprecationsMockResponse.cluster[0].message
.split(' ')
.join('_')}`;
@@ -180,7 +180,7 @@ describe('Cluster tab', () => {
component.update();
const request = server.requests[server.requests.length - 1];
- const mlDeprecation = upgradeStatusMockResponse.cluster[0];
+ const mlDeprecation = esDeprecationsMockResponse.cluster[0];
expect(request.method).toBe('DELETE');
expect(request.url).toBe(
@@ -212,7 +212,7 @@ describe('Cluster tab', () => {
component.update();
const request = server.requests[server.requests.length - 1];
- const mlDeprecation = upgradeStatusMockResponse.cluster[0];
+ const mlDeprecation = esDeprecationsMockResponse.cluster[0];
expect(request.method).toBe('DELETE');
expect(request.url).toBe(
@@ -221,7 +221,7 @@ describe('Cluster tab', () => {
}/${(mlDeprecation.correctiveAction! as MlAction).snapshotId}`
);
- const accordionTestSubj = `depgroup_${upgradeStatusMockResponse.cluster[0].message
+ const accordionTestSubj = `depgroup_${esDeprecationsMockResponse.cluster[0].message
.split(' ')
.join('_')}`;
@@ -233,7 +233,7 @@ describe('Cluster tab', () => {
describe('no deprecations', () => {
beforeEach(async () => {
const noDeprecationsResponse = {
- readyForUpgrade: false,
+ totalCriticalDeprecations: 0,
cluster: [],
indices: [],
};
diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts
index 3fd8b7279c073..f1f21b430d31b 100644
--- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts
+++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts
@@ -7,19 +7,16 @@
import sinon, { SinonFakeServer } from 'sinon';
import { API_BASE_PATH } from '../../../common/constants';
-import { UpgradeAssistantStatus } from '../../../common/types';
+import { ESUpgradeStatus } from '../../../common/types';
import { ResponseError } from '../../../public/application/lib/api';
// Register helpers to mock HTTP Requests
const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
- const setLoadEsDeprecationsResponse = (
- response?: UpgradeAssistantStatus,
- error?: ResponseError
- ) => {
+ const setLoadEsDeprecationsResponse = (response?: ESUpgradeStatus, error?: ResponseError) => {
const status = error ? error.statusCode || 400 : 200;
const body = error ? error : response;
- server.respondWith('GET', `${API_BASE_PATH}/status`, [
+ server.respondWith('GET', `${API_BASE_PATH}/es_deprecations`, [
status,
{ 'Content-Type': 'application/json' },
JSON.stringify(body),
diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/indices.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/indices.test.ts
index b44a04eb15d86..a959473bc4ec6 100644
--- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/indices.test.ts
+++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/indices.test.ts
@@ -7,7 +7,7 @@
import { act } from 'react-dom/test-utils';
import { indexSettingDeprecations } from '../../common/constants';
-import { UpgradeAssistantStatus } from '../../common/types';
+import { ESUpgradeStatus } from '../../common/types';
import { IndicesTestBed, setupIndicesPage, setupEnvironment } from './helpers';
@@ -20,8 +20,8 @@ describe('Indices tab', () => {
});
describe('with deprecations', () => {
- const upgradeStatusMockResponse: UpgradeAssistantStatus = {
- readyForUpgrade: false,
+ const esDeprecationsMockResponse: ESUpgradeStatus = {
+ totalCriticalDeprecations: 0,
cluster: [],
indices: [
{
@@ -38,7 +38,7 @@ describe('Indices tab', () => {
};
beforeEach(async () => {
- httpRequestsMockHelpers.setLoadEsDeprecationsResponse(upgradeStatusMockResponse);
+ httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse);
httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({ isEnabled: true });
await act(async () => {
@@ -93,7 +93,7 @@ describe('Indices tab', () => {
expect(modal).not.toBe(null);
expect(modal!.textContent).toContain('Remove deprecated settings');
- const indexName = upgradeStatusMockResponse.indices[0].index;
+ const indexName = esDeprecationsMockResponse.indices[0].index;
httpRequestsMockHelpers.setUpdateIndexSettingsResponse({
acknowledged: true,
@@ -117,7 +117,7 @@ describe('Indices tab', () => {
describe('no deprecations', () => {
beforeEach(async () => {
const noDeprecationsResponse = {
- readyForUpgrade: false,
+ totalCriticalDeprecations: 0,
cluster: [],
indices: [],
};
diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview.test.ts
index 9b65b493a74c4..e86917dd4bd5d 100644
--- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview.test.ts
+++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview.test.ts
@@ -8,7 +8,7 @@
import type { DomainDeprecationDetails } from 'kibana/public';
import { act } from 'react-dom/test-utils';
import { deprecationsServiceMock } from 'src/core/public/mocks';
-import { UpgradeAssistantStatus } from '../../common/types';
+import { ESUpgradeStatus } from '../../common/types';
import { OverviewTestBed, setupOverviewPage, setupEnvironment } from './helpers';
@@ -17,8 +17,8 @@ describe('Overview page', () => {
const { server, httpRequestsMockHelpers } = setupEnvironment();
beforeEach(async () => {
- const esDeprecationsMockResponse: UpgradeAssistantStatus = {
- readyForUpgrade: false,
+ const esDeprecationsMockResponse: ESUpgradeStatus = {
+ totalCriticalDeprecations: 1,
cluster: [
{
level: 'critical',
diff --git a/x-pack/plugins/upgrade_assistant/common/types.ts b/x-pack/plugins/upgrade_assistant/common/types.ts
index a02e3a2309e77..680e1e03dbbf0 100644
--- a/x-pack/plugins/upgrade_assistant/common/types.ts
+++ b/x-pack/plugins/upgrade_assistant/common/types.ts
@@ -220,8 +220,8 @@ export interface EnrichedDeprecationInfo extends DeprecationInfo {
correctiveAction?: ReindexAction | MlAction | IndexSettingAction;
}
-export interface UpgradeAssistantStatus {
- readyForUpgrade: boolean;
+export interface ESUpgradeStatus {
+ totalCriticalDeprecations: number;
cluster: EnrichedDeprecationInfo[];
indices: EnrichedDeprecationInfo[];
}
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/types.ts b/x-pack/plugins/upgrade_assistant/public/application/components/types.ts
index 8e2bf20b845a3..e3ba609026a8a 100644
--- a/x-pack/plugins/upgrade_assistant/public/application/components/types.ts
+++ b/x-pack/plugins/upgrade_assistant/public/application/components/types.ts
@@ -7,12 +7,12 @@
import React from 'react';
-import { EnrichedDeprecationInfo, UpgradeAssistantStatus } from '../../../common/types';
+import { EnrichedDeprecationInfo, ESUpgradeStatus } from '../../../common/types';
import { ResponseError } from '../lib/api';
export interface UpgradeAssistantTabProps {
alertBanner?: React.ReactNode;
- checkupData?: UpgradeAssistantStatus | null;
+ checkupData?: ESUpgradeStatus | null;
deprecations?: EnrichedDeprecationInfo[];
refreshCheckupData: () => void;
error: ResponseError | null;
diff --git a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts
index c4d9128baa56a..063b53fecb983 100644
--- a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts
+++ b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts
@@ -6,7 +6,7 @@
*/
import { HttpSetup } from 'src/core/public';
-import { UpgradeAssistantStatus } from '../../../common/types';
+import { ESUpgradeStatus } from '../../../common/types';
import { API_BASE_PATH } from '../../../common/constants';
import {
UseRequestConfig,
@@ -46,8 +46,8 @@ export class ApiService {
}
public useLoadUpgradeStatus() {
- return this.useRequest({
- path: `${API_BASE_PATH}/status`,
+ return this.useRequest({
+ path: `${API_BASE_PATH}/es_deprecations`,
method: 'get',
});
}
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap b/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_deprecations_status.test.ts.snap
similarity index 98%
rename from x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap
rename to x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_deprecations_status.test.ts.snap
index a7890adf1f0eb..3e847eef18f07 100644
--- a/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap
+++ b/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_deprecations_status.test.ts.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`getUpgradeAssistantStatus returns the correct shape of data 1`] = `
+exports[`getESUpgradeStatus returns the correct shape of data 1`] = `
Object {
"cluster": Array [
Object {
@@ -129,6 +129,6 @@ Object {
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
},
],
- "readyForUpgrade": false,
+ "totalCriticalDeprecations": 4,
}
`;
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/kibana_status.test.ts.snap b/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/kibana_status.test.ts.snap
new file mode 100644
index 0000000000000..d2aad8d5e6c47
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/kibana_status.test.ts.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`getKibanaUpgradeStatus returns the correct shape of data 1`] = `
+Object {
+ "totalCriticalDeprecations": 1,
+}
+`;
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.test.ts
similarity index 80%
rename from x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts
rename to x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.test.ts
index 6477ce738c084..f87a8916e1a52 100644
--- a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts
+++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.test.ts
@@ -10,7 +10,7 @@ import { RequestEvent } from '@elastic/elasticsearch/lib/Transport';
import { elasticsearchServiceMock } from 'src/core/server/mocks';
import { DeprecationAPIResponse } from '../../common/types';
-import { getUpgradeAssistantStatus } from './es_migration_apis';
+import { getESUpgradeStatus } from './es_deprecations_status';
import fakeDeprecations from './__fixtures__/fake_deprecations.json';
const fakeIndexNames = Object.keys(fakeDeprecations.index_settings);
@@ -20,7 +20,7 @@ const asApiResponse = (body: T): RequestEvent =>
body,
} as RequestEvent);
-describe('getUpgradeAssistantStatus', () => {
+describe('getESUpgradeStatus', () => {
const resolvedIndices = {
indices: fakeIndexNames.map((indexName) => {
// mark one index as closed to test blockerForReindexing flag
@@ -45,16 +45,16 @@ describe('getUpgradeAssistantStatus', () => {
esClient.asCurrentUser.indices.resolveIndex.mockResolvedValue(asApiResponse(resolvedIndices));
it('calls /_migration/deprecations', async () => {
- await getUpgradeAssistantStatus(esClient);
+ await getESUpgradeStatus(esClient);
expect(esClient.asCurrentUser.migration.deprecations).toHaveBeenCalled();
});
it('returns the correct shape of data', async () => {
- const resp = await getUpgradeAssistantStatus(esClient);
+ const resp = await getESUpgradeStatus(esClient);
expect(resp).toMatchSnapshot();
});
- it('returns readyForUpgrade === false when critical issues found', async () => {
+ it('returns totalCriticalDeprecations > 0 when critical issues found', async () => {
esClient.asCurrentUser.migration.deprecations.mockResolvedValue(
// @ts-expect-error not full interface
asApiResponse({
@@ -65,13 +65,13 @@ describe('getUpgradeAssistantStatus', () => {
})
);
- await expect(getUpgradeAssistantStatus(esClient)).resolves.toHaveProperty(
- 'readyForUpgrade',
- false
+ await expect(getESUpgradeStatus(esClient)).resolves.toHaveProperty(
+ 'totalCriticalDeprecations',
+ 1
);
});
- it('returns readyForUpgrade === true when no critical issues found', async () => {
+ it('returns totalCriticalDeprecations === 0 when no critical issues found', async () => {
esClient.asCurrentUser.migration.deprecations.mockResolvedValue(
// @ts-expect-error not full interface
asApiResponse({
@@ -82,9 +82,9 @@ describe('getUpgradeAssistantStatus', () => {
})
);
- await expect(getUpgradeAssistantStatus(esClient)).resolves.toHaveProperty(
- 'readyForUpgrade',
- true
+ await expect(getESUpgradeStatus(esClient)).resolves.toHaveProperty(
+ 'totalCriticalDeprecations',
+ 0
);
});
});
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.ts
similarity index 93%
rename from x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts
rename to x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.ts
index d5c112125ccce..b87d63ae36ec1 100644
--- a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts
+++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.ts
@@ -10,23 +10,24 @@ import { indexSettingDeprecations } from '../../common/constants';
import {
DeprecationAPIResponse,
EnrichedDeprecationInfo,
- UpgradeAssistantStatus,
+ ESUpgradeStatus,
} from '../../common/types';
import { esIndicesStateCheck } from './es_indices_state_check';
-export async function getUpgradeAssistantStatus(
+export async function getESUpgradeStatus(
dataClient: IScopedClusterClient
-): Promise {
+): Promise {
const { body: deprecations } = await dataClient.asCurrentUser.migration.deprecations();
const cluster = getClusterDeprecations(deprecations);
const indices = await getCombinedIndexInfos(deprecations, dataClient);
- const criticalWarnings = cluster.concat(indices).filter((d) => d.level === 'critical');
+ const totalCriticalDeprecations = cluster.concat(indices).filter((d) => d.level === 'critical')
+ .length;
return {
- readyForUpgrade: criticalWarnings.length === 0,
+ totalCriticalDeprecations,
cluster,
indices,
};
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/kibana_status.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/kibana_status.test.ts
new file mode 100644
index 0000000000000..c1bfbf6c4cbfa
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/server/lib/kibana_status.test.ts
@@ -0,0 +1,58 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import _ from 'lodash';
+import { deprecationsServiceMock } from 'src/core/server/mocks';
+import { DomainDeprecationDetails } from 'src/core/server/types';
+
+import { getKibanaUpgradeStatus } from './kibana_status';
+
+const mockKibanaDeprecations: DomainDeprecationDetails[] = [
+ {
+ correctiveActions: {
+ manualSteps: [
+ 'Using Kibana user management, change all users using the kibana_user role to the kibana_admin role.',
+ 'Using Kibana role-mapping management, change all role-mappings which assing the kibana_user role to the kibana_admin role.',
+ ],
+ },
+ deprecationType: 'config',
+ documentationUrl: 'testDocUrl',
+ level: 'critical',
+ message: 'testMessage',
+ requireRestart: true,
+ domainId: 'security',
+ },
+];
+
+describe('getKibanaUpgradeStatus', () => {
+ const deprecationsClient = deprecationsServiceMock.createClient();
+
+ deprecationsClient.getAllDeprecations.mockResolvedValue(mockKibanaDeprecations);
+
+ it('returns the correct shape of data', async () => {
+ const resp = await getKibanaUpgradeStatus(deprecationsClient);
+ expect(resp).toMatchSnapshot();
+ });
+
+ it('returns totalCriticalDeprecations > 0 when critical issues found', async () => {
+ deprecationsClient.getAllDeprecations.mockResolvedValue(mockKibanaDeprecations);
+
+ await expect(getKibanaUpgradeStatus(deprecationsClient)).resolves.toHaveProperty(
+ 'totalCriticalDeprecations',
+ 1
+ );
+ });
+
+ it('returns totalCriticalDeprecations === 0 when no critical issues found', async () => {
+ deprecationsClient.getAllDeprecations.mockResolvedValue([]);
+
+ await expect(getKibanaUpgradeStatus(deprecationsClient)).resolves.toHaveProperty(
+ 'totalCriticalDeprecations',
+ 0
+ );
+ });
+});
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/kibana_status.ts b/x-pack/plugins/upgrade_assistant/server/lib/kibana_status.ts
new file mode 100644
index 0000000000000..2d48182e6fd6b
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/server/lib/kibana_status.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { DeprecationsClient } from 'kibana/server';
+import { DomainDeprecationDetails } from 'src/core/server/types';
+
+export const getKibanaUpgradeStatus = async (deprecationsClient: DeprecationsClient) => {
+ const kibanaDeprecations: DomainDeprecationDetails[] = await deprecationsClient.getAllDeprecations();
+
+ const totalCriticalDeprecations = kibanaDeprecations.filter((d) => d.level === 'critical').length;
+
+ return {
+ totalCriticalDeprecations,
+ };
+};
diff --git a/x-pack/plugins/upgrade_assistant/server/plugin.ts b/x-pack/plugins/upgrade_assistant/server/plugin.ts
index 50b7330b4d466..8fb677feabd1b 100644
--- a/x-pack/plugins/upgrade_assistant/server/plugin.ts
+++ b/x-pack/plugins/upgrade_assistant/server/plugin.ts
@@ -94,13 +94,13 @@ export class UpgradeAssistantServerPlugin implements Plugin {
router,
credentialStore: this.credentialStore,
log: this.logger,
+ licensing,
getSavedObjectsService: () => {
if (!this.savedObjectsServiceStart) {
throw new Error('Saved Objects Start service not available');
}
return this.savedObjectsServiceStart;
},
- licensing,
};
// Initialize version service with current kibana version
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts
index 09da52e4b6ffd..8a62a12778883 100644
--- a/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts
+++ b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts
@@ -9,6 +9,7 @@ import { RequestHandler, RequestHandlerContext } from 'src/core/server';
import {
elasticsearchServiceMock,
savedObjectsClientMock,
+ deprecationsServiceMock,
} from '../../../../../../src/core/server/mocks';
export const routeHandlerContextMock = ({
@@ -17,6 +18,7 @@ export const routeHandlerContextMock = ({
client: elasticsearchServiceMock.createScopedClusterClient(),
},
savedObjects: { client: savedObjectsClientMock.create() },
+ deprecations: { client: deprecationsServiceMock.createClient() },
},
} as unknown) as RequestHandlerContext;
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/es_deprecations.test.ts
similarity index 73%
rename from x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts
rename to x-pack/plugins/upgrade_assistant/server/routes/es_deprecations.test.ts
index 934fdb1c4eb37..9603eae18d9c1 100644
--- a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts
+++ b/x-pack/plugins/upgrade_assistant/server/routes/es_deprecations.test.ts
@@ -15,17 +15,17 @@ jest.mock('../lib/es_version_precheck', () => ({
// Need to require to get mock on named export to work.
// eslint-disable-next-line @typescript-eslint/no-var-requires
-const MigrationApis = require('../lib/es_migration_apis');
-MigrationApis.getUpgradeAssistantStatus = jest.fn();
+const ESUpgradeStatusApis = require('../lib/es_deprecations_status');
+ESUpgradeStatusApis.getESUpgradeStatus = jest.fn();
-import { registerClusterCheckupRoutes } from './cluster_checkup';
+import { registerESDeprecationRoutes } from './es_deprecations';
/**
* Since these route callbacks are so thin, these serve simply as integration tests
* to ensure they're wired up to the lib functions correctly. Business logic is tested
- * more thoroughly in the es_migration_apis test.
+ * more thoroughly in the es_deprecations_status test.
*/
-describe('cluster checkup API', () => {
+describe('ES deprecations API', () => {
let mockRouter: MockRouter;
let routeDependencies: any;
@@ -34,23 +34,23 @@ describe('cluster checkup API', () => {
routeDependencies = {
router: mockRouter,
};
- registerClusterCheckupRoutes(routeDependencies);
+ registerESDeprecationRoutes(routeDependencies);
});
afterEach(() => {
jest.resetAllMocks();
});
- describe('GET /api/upgrade_assistant/reindex/{indexName}.json', () => {
+ describe('GET /api/upgrade_assistant/es_deprecations', () => {
it('returns state', async () => {
- MigrationApis.getUpgradeAssistantStatus.mockResolvedValue({
+ ESUpgradeStatusApis.getESUpgradeStatus.mockResolvedValue({
cluster: [],
indices: [],
nodes: [],
});
const resp = await routeDependencies.router.getHandler({
method: 'get',
- pathPattern: '/api/upgrade_assistant/status',
+ pathPattern: '/api/upgrade_assistant/es_deprecations',
})(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory);
expect(resp.status).toEqual(200);
@@ -63,22 +63,22 @@ describe('cluster checkup API', () => {
const e: any = new Error(`you can't go here!`);
e.statusCode = 403;
- MigrationApis.getUpgradeAssistantStatus.mockRejectedValue(e);
+ ESUpgradeStatusApis.getESUpgradeStatus.mockRejectedValue(e);
const resp = await routeDependencies.router.getHandler({
method: 'get',
- pathPattern: '/api/upgrade_assistant/status',
+ pathPattern: '/api/upgrade_assistant/es_deprecations',
})(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory);
expect(resp.status).toEqual(403);
});
it('returns an 500 error if it throws', async () => {
- MigrationApis.getUpgradeAssistantStatus.mockRejectedValue(new Error(`scary error!`));
+ ESUpgradeStatusApis.getESUpgradeStatus.mockRejectedValue(new Error('scary error!'));
await expect(
routeDependencies.router.getHandler({
method: 'get',
- pathPattern: '/api/upgrade_assistant/status',
+ pathPattern: '/api/upgrade_assistant/es_deprecations',
})(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory)
).rejects.toThrow('scary error!');
});
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts b/x-pack/plugins/upgrade_assistant/server/routes/es_deprecations.ts
similarity index 86%
rename from x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts
rename to x-pack/plugins/upgrade_assistant/server/routes/es_deprecations.ts
index 31026be55fa30..395fa04af9173 100644
--- a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts
+++ b/x-pack/plugins/upgrade_assistant/server/routes/es_deprecations.ts
@@ -6,16 +6,16 @@
*/
import { API_BASE_PATH } from '../../common/constants';
-import { getUpgradeAssistantStatus } from '../lib/es_migration_apis';
+import { getESUpgradeStatus } from '../lib/es_deprecations_status';
import { versionCheckHandlerWrapper } from '../lib/es_version_precheck';
import { RouteDependencies } from '../types';
import { reindexActionsFactory } from '../lib/reindexing/reindex_actions';
import { reindexServiceFactory } from '../lib/reindexing';
-export function registerClusterCheckupRoutes({ router, licensing, log }: RouteDependencies) {
+export function registerESDeprecationRoutes({ router, licensing, log }: RouteDependencies) {
router.get(
{
- path: `${API_BASE_PATH}/status`,
+ path: `${API_BASE_PATH}/es_deprecations`,
validate: false,
},
versionCheckHandlerWrapper(
@@ -30,7 +30,7 @@ export function registerClusterCheckupRoutes({ router, licensing, log }: RouteDe
response
) => {
try {
- const status = await getUpgradeAssistantStatus(client);
+ const status = await getESUpgradeStatus(client);
const asCurrentUser = client.asCurrentUser;
const reindexActions = reindexActionsFactory(savedObjectsClient, asCurrentUser);
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts b/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts
index 50cb9257462b9..332db10805692 100644
--- a/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts
+++ b/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts
@@ -7,19 +7,22 @@
import { RouteDependencies } from '../types';
-import { registerClusterCheckupRoutes } from './cluster_checkup';
+import { registerESDeprecationRoutes } from './es_deprecations';
import { registerDeprecationLoggingRoutes } from './deprecation_logging';
import { registerReindexIndicesRoutes } from './reindex_indices';
import { registerTelemetryRoutes } from './telemetry';
import { registerUpdateSettingsRoute } from './update_index_settings';
import { registerMlSnapshotRoutes } from './ml_snapshots';
import { ReindexWorker } from '../lib/reindexing';
+import { registerUpgradeStatusRoute } from './status';
export function registerRoutes(dependencies: RouteDependencies, getWorker: () => ReindexWorker) {
- registerClusterCheckupRoutes(dependencies);
+ registerESDeprecationRoutes(dependencies);
registerDeprecationLoggingRoutes(dependencies);
registerReindexIndicesRoutes(dependencies, getWorker);
registerTelemetryRoutes(dependencies);
registerUpdateSettingsRoute(dependencies);
registerMlSnapshotRoutes(dependencies);
+ // Route for cloud to retrieve the upgrade status for ES and Kibana
+ registerUpgradeStatusRoute(dependencies);
}
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/status.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/status.test.ts
new file mode 100644
index 0000000000000..bd5299ad8a4f3
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/server/routes/status.test.ts
@@ -0,0 +1,119 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { kibanaResponseFactory } from 'src/core/server';
+import { createMockRouter, MockRouter, routeHandlerContextMock } from './__mocks__/routes.mock';
+import { createRequestMock } from './__mocks__/request.mock';
+import { registerUpgradeStatusRoute } from './status';
+
+jest.mock('../lib/es_version_precheck', () => ({
+ versionCheckHandlerWrapper: (a: any) => a,
+}));
+
+// Need to require to get mock on named export to work.
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const ESUpgradeStatusApis = require('../lib/es_deprecations_status');
+ESUpgradeStatusApis.getESUpgradeStatus = jest.fn();
+
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const KibanaUpgradeStatusApis = require('../lib/kibana_status');
+KibanaUpgradeStatusApis.getKibanaUpgradeStatus = jest.fn();
+
+describe('Status API', () => {
+ let mockRouter: MockRouter;
+ let routeDependencies: any;
+
+ beforeEach(() => {
+ mockRouter = createMockRouter();
+ routeDependencies = {
+ router: mockRouter,
+ };
+ registerUpgradeStatusRoute(routeDependencies);
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ describe('GET /api/upgrade_assistant/status', () => {
+ it('returns readyForUpgrade === false if Kibana or ES contain critical deprecations', async () => {
+ ESUpgradeStatusApis.getESUpgradeStatus.mockResolvedValue({
+ cluster: [
+ {
+ level: 'critical',
+ message:
+ 'model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded',
+ details:
+ 'model snapshot [%s] for job [%s] supports minimum version [%s] and needs to be at least [%s]',
+ url: 'doc_url',
+ correctiveAction: {
+ type: 'mlSnapshot',
+ snapshotId: '1',
+ jobId: 'deprecation_check_job',
+ },
+ },
+ ],
+ indices: [],
+ totalCriticalDeprecations: 1,
+ });
+
+ KibanaUpgradeStatusApis.getKibanaUpgradeStatus.mockResolvedValue({
+ totalCriticalDeprecations: 1,
+ });
+
+ const resp = await routeDependencies.router.getHandler({
+ method: 'get',
+ pathPattern: '/api/upgrade_assistant/status',
+ })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory);
+
+ expect(resp.status).toEqual(200);
+ expect(resp.payload).toEqual({
+ readyForUpgrade: false,
+ details:
+ 'You have 1 Elasticsearch deprecation issues and 1 Kibana deprecation issues that must be resolved before upgrading.',
+ });
+ });
+
+ it('returns readyForUpgrade === true if there are no critical deprecations', async () => {
+ ESUpgradeStatusApis.getESUpgradeStatus.mockResolvedValue({
+ cluster: [],
+ indices: [],
+ totalCriticalDeprecations: 0,
+ });
+
+ KibanaUpgradeStatusApis.getKibanaUpgradeStatus.mockResolvedValue({
+ totalCriticalDeprecations: 0,
+ });
+
+ const resp = await routeDependencies.router.getHandler({
+ method: 'get',
+ pathPattern: '/api/upgrade_assistant/status',
+ })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory);
+
+ expect(resp.status).toEqual(200);
+ expect(resp.payload).toEqual({
+ readyForUpgrade: true,
+ details: 'All deprecation issues have been resolved.',
+ });
+ });
+
+ it('returns an error if it throws', async () => {
+ ESUpgradeStatusApis.getESUpgradeStatus.mockRejectedValue(new Error('test error'));
+
+ KibanaUpgradeStatusApis.getKibanaUpgradeStatus.mockResolvedValue({
+ totalCriticalDeprecations: 0,
+ });
+
+ await expect(
+ routeDependencies.router.getHandler({
+ method: 'get',
+ pathPattern: '/api/upgrade_assistant/status',
+ })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory)
+ ).rejects.toThrow('test error');
+ });
+ });
+});
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/status.ts b/x-pack/plugins/upgrade_assistant/server/routes/status.ts
new file mode 100644
index 0000000000000..6684c97a0a83d
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/server/routes/status.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { API_BASE_PATH } from '../../common/constants';
+import { getESUpgradeStatus } from '../lib/es_deprecations_status';
+import { versionCheckHandlerWrapper } from '../lib/es_version_precheck';
+import { getKibanaUpgradeStatus } from '../lib/kibana_status';
+import { RouteDependencies } from '../types';
+import { handleEsError } from '../shared_imports';
+
+export function registerUpgradeStatusRoute({ router }: RouteDependencies) {
+ router.get(
+ {
+ path: `${API_BASE_PATH}/status`,
+ validate: false,
+ },
+ versionCheckHandlerWrapper(
+ async (
+ {
+ core: {
+ elasticsearch: { client: esClient },
+ deprecations: { client: deprecationsClient },
+ },
+ },
+ request,
+ response
+ ) => {
+ try {
+ // Fetch ES upgrade status
+ const { totalCriticalDeprecations: esTotalCriticalDeps } = await getESUpgradeStatus(
+ esClient
+ );
+ // Fetch Kibana upgrade status
+ const {
+ totalCriticalDeprecations: kibanaTotalCriticalDeps,
+ } = await getKibanaUpgradeStatus(deprecationsClient);
+ const readyForUpgrade = esTotalCriticalDeps === 0 && kibanaTotalCriticalDeps === 0;
+
+ const getStatusMessage = () => {
+ if (readyForUpgrade) {
+ return i18n.translate(
+ 'xpack.upgradeAssistant.status.allDeprecationsResolvedMessage',
+ {
+ defaultMessage: 'All deprecation issues have been resolved.',
+ }
+ );
+ }
+
+ return i18n.translate('xpack.upgradeAssistant.status.deprecationsUnresolvedMessage', {
+ defaultMessage:
+ 'You have {esTotalCriticalDeps} Elasticsearch deprecation issues and {kibanaTotalCriticalDeps} Kibana deprecation issues that must be resolved before upgrading.',
+ values: { esTotalCriticalDeps, kibanaTotalCriticalDeps },
+ });
+ };
+
+ return response.ok({
+ body: {
+ readyForUpgrade,
+ details: getStatusMessage(),
+ },
+ });
+ } catch (e) {
+ return handleEsError({ error: e, response });
+ }
+ }
+ )
+ );
+}
diff --git a/x-pack/plugins/uptime/common/constants/rest_api.ts b/x-pack/plugins/uptime/common/constants/rest_api.ts
index 6a75686266533..b49ccc3222a07 100644
--- a/x-pack/plugins/uptime/common/constants/rest_api.ts
+++ b/x-pack/plugins/uptime/common/constants/rest_api.ts
@@ -26,9 +26,9 @@ export enum API_URLS {
ML_CAPABILITIES = '/api/ml/ml_capabilities',
ML_ANOMALIES_RESULT = `/api/ml/results/anomalies_table_data`,
- ALERT_ACTIONS = '/api/actions',
- CREATE_ALERT = '/api/alerts/alert',
- ALERT = '/api/alerts/alert/',
- ALERTS_FIND = '/api/alerts/_find',
- ACTION_TYPES = '/api/actions/list_action_types',
+ RULE_CONNECTORS = '/api/actions/connectors',
+ CREATE_RULE = '/api/alerting/rule',
+ DELETE_RULE = '/api/alerting/rule/',
+ RULES_FIND = '/api/alerting/rules/_find',
+ CONNECTOR_TYPES = '/api/actions/connector_types',
}
diff --git a/x-pack/plugins/uptime/public/state/api/alert_actions.test.ts b/x-pack/plugins/uptime/public/state/api/alert_actions.test.ts
index 15fb95f5d4a4d..96c581f7fab98 100644
--- a/x-pack/plugins/uptime/public/state/api/alert_actions.test.ts
+++ b/x-pack/plugins/uptime/public/state/api/alert_actions.test.ts
@@ -58,7 +58,6 @@ describe('Alert Actions factory', () => {
});
expect(resp).toEqual([
{
- actionTypeId: '.pagerduty',
group: 'recovered',
id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9',
params: {
@@ -69,7 +68,6 @@ describe('Alert Actions factory', () => {
},
},
{
- actionTypeId: '.pagerduty',
group: 'xpack.uptime.alerts.actionGroups.monitorStatus',
id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9',
params: {
@@ -103,7 +101,6 @@ describe('Alert Actions factory', () => {
});
expect(resp).toEqual([
{
- actionTypeId: '.pagerduty',
group: 'recovered',
id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9',
params: {
@@ -114,7 +111,6 @@ describe('Alert Actions factory', () => {
},
},
{
- actionTypeId: '.pagerduty',
group: 'xpack.uptime.alerts.actionGroups.monitorStatus',
id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9',
params: {
diff --git a/x-pack/plugins/uptime/public/state/api/alert_actions.ts b/x-pack/plugins/uptime/public/state/api/alert_actions.ts
index fff5fa9a67804..b0f5f3ea490e5 100644
--- a/x-pack/plugins/uptime/public/state/api/alert_actions.ts
+++ b/x-pack/plugins/uptime/public/state/api/alert_actions.ts
@@ -33,6 +33,8 @@ export const WEBHOOK_ACTION_ID: ActionTypeId = '.webhook';
const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS;
+export type RuleAction = Omit;
+
const getRecoveryMessage = (selectedMonitor: Ping) => {
return i18n.translate('xpack.uptime.alerts.monitorStatus.recoveryMessage', {
defaultMessage: 'Monitor {monitor} with url {url} has recovered with status Up',
@@ -44,18 +46,16 @@ const getRecoveryMessage = (selectedMonitor: Ping) => {
};
export function populateAlertActions({ defaultActions, selectedMonitor }: NewAlertParams) {
- const actions: AlertAction[] = [];
+ const actions: RuleAction[] = [];
defaultActions.forEach((aId) => {
- const action: AlertAction = {
+ const action: RuleAction = {
id: aId.id,
- actionTypeId: aId.actionTypeId,
group: MONITOR_STATUS.id,
params: {},
};
- const recoveredAction: AlertAction = {
+ const recoveredAction: RuleAction = {
id: aId.id,
- actionTypeId: aId.actionTypeId,
group: 'recovered',
params: {
message: getRecoveryMessage(selectedMonitor),
diff --git a/x-pack/plugins/uptime/public/state/api/alerts.ts b/x-pack/plugins/uptime/public/state/api/alerts.ts
index 5931936c48163..d74a73befb5d0 100644
--- a/x-pack/plugins/uptime/public/state/api/alerts.ts
+++ b/x-pack/plugins/uptime/public/state/api/alerts.ts
@@ -10,18 +10,35 @@ import { apiService } from './utils';
import { ActionConnector } from '../alerts/alerts';
import { AlertsResult, MonitorIdParam } from '../actions/types';
-import { ActionType, AlertAction } from '../../../../triggers_actions_ui/public';
+import { ActionType, AsApiContract } from '../../../../triggers_actions_ui/public';
import { API_URLS } from '../../../common/constants';
import { Alert, AlertTypeParams } from '../../../../alerting/common';
import { AtomicStatusCheckParams } from '../../../common/runtime_types/alerts';
-import { populateAlertActions } from './alert_actions';
+import { populateAlertActions, RuleAction } from './alert_actions';
import { Ping } from '../../../common/runtime_types/ping';
const UPTIME_AUTO_ALERT = 'UPTIME_AUTO';
-export const fetchConnectors = async () => {
- return await apiService.get(API_URLS.ALERT_ACTIONS);
+export const fetchConnectors = async (): Promise => {
+ const response = (await apiService.get(API_URLS.RULE_CONNECTORS)) as Array<
+ AsApiContract
+ >;
+ return response.map(
+ ({
+ connector_type_id: actionTypeId,
+ referenced_by_count: referencedByCount,
+ is_preconfigured: isPreconfigured,
+ is_missing_secrets: isMissingSecrets,
+ ...res
+ }) => ({
+ ...res,
+ actionTypeId,
+ referencedByCount,
+ isPreconfigured,
+ isMissingSecrets,
+ })
+ );
};
export interface NewAlertParams extends AlertTypeParams {
@@ -41,14 +58,21 @@ type NewMonitorStatusAlert = Omit<
| 'muteAll'
| 'mutedInstanceIds'
| 'executionStatus'
->;
+ | 'alertTypeId'
+ | 'notifyWhen'
+ | 'actions'
+> & {
+ rule_type_id: Alert['alertTypeId'];
+ notify_when: Alert['notifyWhen'];
+ actions: RuleAction[];
+};
export const createAlert = async ({
defaultActions,
monitorId,
selectedMonitor,
}: NewAlertParams): Promise => {
- const actions: AlertAction[] = populateAlertActions({
+ const actions: RuleAction[] = populateAlertActions({
defaultActions,
selectedMonitor,
});
@@ -66,16 +90,16 @@ export const createAlert = async ({
filters: { 'url.port': [], 'observer.geo.name': [], 'monitor.type': [], tags: [] },
},
consumer: 'uptime',
- alertTypeId: CLIENT_ALERT_TYPES.MONITOR_STATUS,
+ rule_type_id: CLIENT_ALERT_TYPES.MONITOR_STATUS,
schedule: { interval: '1m' },
- notifyWhen: 'onActionGroupChange',
+ notify_when: 'onActionGroupChange',
tags: [UPTIME_AUTO_ALERT],
name: `${selectedMonitor?.monitor.name || selectedMonitor?.url?.full}(Simple status alert)`,
enabled: true,
throttle: null,
};
- return await apiService.post(API_URLS.CREATE_ALERT, data);
+ return await apiService.post(API_URLS.CREATE_RULE, data);
};
export const fetchMonitorAlertRecords = async (): Promise => {
@@ -89,7 +113,7 @@ export const fetchMonitorAlertRecords = async (): Promise => {
search_fields: ['name', 'tags'],
search: 'UPTIME_AUTO',
};
- return await apiService.get(API_URLS.ALERTS_FIND, data);
+ return await apiService.get(API_URLS.RULES_FIND, data);
};
export const fetchAlertRecords = async ({
@@ -103,14 +127,29 @@ export const fetchAlertRecords = async ({
sort_field: 'name.keyword',
sort_order: 'asc',
};
- const alerts = await apiService.get(API_URLS.ALERTS_FIND, data);
+ const alerts = await apiService.get(API_URLS.RULES_FIND, data);
return alerts.data.find((alert: Alert) => alert.params.monitorId === monitorId);
};
export const disableAlertById = async ({ alertId }: { alertId: string }) => {
- return await apiService.delete(API_URLS.ALERT + alertId);
+ return await apiService.delete(API_URLS.DELETE_RULE + alertId);
};
export const fetchActionTypes = async (): Promise => {
- return await apiService.get(API_URLS.ACTION_TYPES);
+ const response = (await apiService.get(API_URLS.CONNECTOR_TYPES)) as Array<
+ AsApiContract
+ >;
+ return response.map(
+ ({
+ enabled_in_config: enabledInConfig,
+ enabled_in_license: enabledInLicense,
+ minimum_license_required: minimumLicenseRequired,
+ ...res
+ }: AsApiContract) => ({
+ ...res,
+ enabledInConfig,
+ enabledInLicense,
+ minimumLicenseRequired,
+ })
+ );
};
diff --git a/x-pack/plugins/uptime/server/plugin.ts b/x-pack/plugins/uptime/server/plugin.ts
index 3e935eab153ac..a201eddc45345 100644
--- a/x-pack/plugins/uptime/server/plugin.ts
+++ b/x-pack/plugins/uptime/server/plugin.ts
@@ -36,8 +36,8 @@ export class Plugin implements PluginType {
const { ruleDataService } = plugins.ruleRegistry;
const ruleDataClient = ruleDataService.initializeIndex({
- feature: 'synthetics',
- registrationContext: 'observability.synthetics',
+ feature: 'uptime',
+ registrationContext: 'observability.uptime',
dataset: Dataset.alerts,
componentTemplateRefs: [],
componentTemplates: [
diff --git a/x-pack/test/api_integration/apis/upgrade_assistant/upgrade_assistant.ts b/x-pack/test/api_integration/apis/upgrade_assistant/upgrade_assistant.ts
index 0eb0b98e29ffe..7dd7a1584d0ef 100644
--- a/x-pack/test/api_integration/apis/upgrade_assistant/upgrade_assistant.ts
+++ b/x-pack/test/api_integration/apis/upgrade_assistant/upgrade_assistant.ts
@@ -35,7 +35,7 @@ export default function ({ getService }: FtrProviderContext) {
});
});
- describe('Update index settings route', () => {
+ describe('POST /api/upgrade_assistant/{indexName}/index_settings', () => {
const indexName = 'update_settings_test_index';
const indexSettings = {
number_of_shards: '3',
@@ -121,5 +121,22 @@ export default function ({ getService }: FtrProviderContext) {
expect(body.error).to.eql('Internal Server Error');
});
});
+
+ describe('GET /api/upgrade_assistant/status', () => {
+ it('returns a successful response', async () => {
+ const { body } = await supertest
+ .get('/api/upgrade_assistant/status')
+ .set('kbn-xsrf', 'xxx')
+ .expect(200);
+
+ const expectedResponseKeys = ['readyForUpgrade', 'details'];
+
+ // We're not able to easily test different upgrade status scenarios (there are tests with mocked data to handle this)
+ // so, for now, we simply verify the response returns the expected format
+ expectedResponseKeys.forEach((key) => {
+ expect(body[key]).to.not.equal(undefined);
+ });
+ });
+ });
});
}
diff --git a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts
index 0d318ea5bc6c7..7ff81defe5ff2 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts
@@ -43,9 +43,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const INDEXING_DELAY = 5000;
const ALERTS_INDEX_TARGET = '.kibana-alerts-observability.apm.alerts*';
- const APM_TRANSACTION_INDEX_NAME = 'apm-8.0.0-transaction';
+ const APM_METRIC_INDEX_NAME = 'apm-8.0.0-transaction';
- const createTransactionEvent = (override: Record) => {
+ const createTransactionMetric = (override: Record) => {
const now = Date.now();
const time = now - INDEXING_DELAY;
@@ -61,12 +61,15 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
transaction: {
duration: {
- us: 1000000,
+ histogram: {
+ values: [1000000],
+ counts: [1],
+ },
},
type: 'request',
},
processor: {
- event: 'transaction',
+ event: 'metric',
},
observer: {
version_major: 7,
@@ -141,7 +144,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
before(async () => {
await es.indices.create({
- index: APM_TRANSACTION_INDEX_NAME,
+ index: APM_METRIC_INDEX_NAME,
body: {
mappings: {
dynamic: 'strict',
@@ -184,8 +187,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
},
duration: {
properties: {
- us: {
- type: 'long',
+ histogram: {
+ type: 'histogram',
},
},
},
@@ -252,7 +255,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
await es.indices.delete({
- index: APM_TRANSACTION_INDEX_NAME,
+ index: APM_METRIC_INDEX_NAME,
});
});
@@ -281,8 +284,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(beforeDataResponse.body.hits.hits.length).to.be(0);
await es.index({
- index: APM_TRANSACTION_INDEX_NAME,
- body: createTransactionEvent({
+ index: APM_METRIC_INDEX_NAME,
+ body: createTransactionMetric({
event: {
outcome: 'success',
},
@@ -310,8 +313,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(afterInitialDataResponse.body.hits.hits.length).to.be(0);
await es.index({
- index: APM_TRANSACTION_INDEX_NAME,
- body: createTransactionEvent({
+ index: APM_METRIC_INDEX_NAME,
+ body: createTransactionMetric({
event: {
outcome: 'failure',
},
@@ -410,16 +413,16 @@ export default function ApiTest({ getService }: FtrProviderContext) {
`);
await es.bulk({
- index: APM_TRANSACTION_INDEX_NAME,
+ index: APM_METRIC_INDEX_NAME,
body: [
{ index: {} },
- createTransactionEvent({
+ createTransactionMetric({
event: {
outcome: 'success',
},
}),
{ index: {} },
- createTransactionEvent({
+ createTransactionMetric({
event: {
outcome: 'success',
},
diff --git a/x-pack/test/functional/apps/infra/metrics_explorer.ts b/x-pack/test/functional/apps/infra/metrics_explorer.ts
index 90a875a07f8a7..5a8d7da628259 100644
--- a/x-pack/test/functional/apps/infra/metrics_explorer.ts
+++ b/x-pack/test/functional/apps/infra/metrics_explorer.ts
@@ -88,7 +88,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
// FLAKY: https://github.com/elastic/kibana/issues/106651
- describe('Saved Views', () => {
+ describe.skip('Saved Views', () => {
before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'));
after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'));
describe('save functionality', () => {
diff --git a/x-pack/test/functional/apps/lens/drag_and_drop.ts b/x-pack/test/functional/apps/lens/drag_and_drop.ts
index 21c0b30a92be3..8dd500755242b 100644
--- a/x-pack/test/functional/apps/lens/drag_and_drop.ts
+++ b/x-pack/test/functional/apps/lens/drag_and_drop.ts
@@ -12,7 +12,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'header']);
describe('lens drag and drop tests', () => {
- describe('basic drag and drop', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/108352
+ describe.skip('basic drag and drop', () => {
it('should construct the basic split xy chart', async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('lens');
diff --git a/x-pack/test/functional/apps/lens/runtime_fields.ts b/x-pack/test/functional/apps/lens/runtime_fields.ts
index c5391f3f2b548..da5634b0ae838 100644
--- a/x-pack/test/functional/apps/lens/runtime_fields.ts
+++ b/x-pack/test/functional/apps/lens/runtime_fields.ts
@@ -14,7 +14,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const fieldEditor = getService('fieldEditor');
const retry = getService('retry');
- describe('lens runtime fields', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/95614
+ describe.skip('lens runtime fields', () => {
it('should be able to add runtime field and use it', async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('lens');
diff --git a/x-pack/test/functional/services/ml/job_annotations_table.ts b/x-pack/test/functional/services/ml/job_annotations_table.ts
index 99e25c20573ae..0acba253cb056 100644
--- a/x-pack/test/functional/services/ml/job_annotations_table.ts
+++ b/x-pack/test/functional/services/ml/job_annotations_table.ts
@@ -136,13 +136,13 @@ export function MachineLearningJobAnnotationsProvider({ getService }: FtrProvide
}
public async assertAnnotationsRowExists(annotationId: string) {
- await retry.tryForTime(1000, async () => {
+ await retry.tryForTime(5 * 1000, async () => {
await testSubjects.existOrFail(this.rowSelector(annotationId));
});
}
public async assertAnnotationsRowMissing(annotationId: string) {
- await retry.tryForTime(1000, async () => {
+ await retry.tryForTime(5 * 1000, async () => {
await testSubjects.missingOrFail(this.rowSelector(annotationId));
});
}
diff --git a/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx b/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx
index bedaca0fc1836..61322261d04d5 100644
--- a/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx
+++ b/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx
@@ -68,7 +68,12 @@ const AppRoot = React.memo(
timelinesPluginSetup.getTGrid &&
timelinesPluginSetup.getTGrid<'standalone'>({
alertConsumers: ALERT_RULE_CONSUMER,
+ appId: 'securitySolution',
type: 'standalone',
+ casePermissions: {
+ read: true,
+ crud: true,
+ },
columns: [],
indexNames: [],
deletedEventIds: [],
diff --git a/yarn.lock b/yarn.lock
index 5582c40b94749..121ba05364a81 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1466,10 +1466,10 @@
resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314"
integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ==
-"@elastic/eui@36.1.0":
- version "36.1.0"
- resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-36.1.0.tgz#99946e193bc9d1616f52cbd16e1a5bc196bd450f"
- integrity sha512-j09Kv5K9YBkb9NkM3+cGparJeYfjLfwyypG27UYvSOLCpOYxS3bPycwmD7QrzPjoTqH+HHzDanHSwJMp6ttabA==
+"@elastic/eui@37.1.1":
+ version "37.1.1"
+ resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-37.1.1.tgz#06bd1e1908b8b56525107a3eca42af036b950ab5"
+ integrity sha512-wEehwi9Ei81ydl8ExDuALbzDH6dgaHqKSlv/3Pmm+BcuW8FW4ctr8K5YPSJ7+5hdOdG0Bps5nnIpCDEinXSsLw==
dependencies:
"@types/chroma-js" "^2.0.0"
"@types/lodash" "^4.14.160"
@@ -8359,7 +8359,7 @@ better-opn@^2.0.0:
dependencies:
open "^7.0.3"
-big-integer@^1.6.16:
+big-integer@^1.6.16, big-integer@^1.6.48:
version "1.6.48"
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e"
integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==
@@ -20390,6 +20390,13 @@ node-sass@^4.14.1:
stdout-stream "^1.4.0"
"true-case-path" "^1.0.2"
+node-sql-parser@^3.6.1:
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/node-sql-parser/-/node-sql-parser-3.6.1.tgz#6f096e9df1f19d1e2daa658d864bd68b0e2cd2c6"
+ integrity sha512-AseDvELmUvL22L6C63DsTuzF+0i/HBIHjJq/uxC7jV3PGpAUib5Oe6oz4sgAniSUMPSZQbZmRore6Na68Sg4Tg==
+ dependencies:
+ big-integer "^1.6.48"
+
node-status-codes@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f"