"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StatefulEventsViewer = void 0; var _securitysolutionDataTable = require("@kbn/securitysolution-data-table"); var _public = require("@kbn/kibana-utils-plugin/public"); var _ruleDataUtils = require("@kbn/rule-data-utils"); var _react = _interopRequireWildcard(require("react")); var _reactRedux = require("react-redux"); var _styledComponents = require("styled-components"); var _lodash = require("lodash"); var _common = require("@kbn/data-plugin/common"); var _constants = require("../../../../common/constants"); var _constants2 = require("../../store/inputs/constants"); var _actions = require("../../store/actions"); var _inspect = require("../inspect"); var _use_full_screen = require("../../containers/use_full_screen"); var _selectors = require("./selectors"); var _sourcerer = require("../../containers/sourcerer"); var _kibana = require("../../lib/kibana"); var _graph_overlay = require("../../../timelines/components/graph_overlay"); var _fields_browser = require("../../../timelines/components/fields_browser"); var _use_session_view = require("../../../timelines/components/timeline/session_tab_content/use_session_view"); var _styles = require("./styles"); var _helpers = require("./helpers"); var _use_timelines_events = require("./use_timelines_events"); var _shared = require("./shared"); var _manage_query = require("../page/manage_query"); var _control_columns = require("../control_columns"); var _right_top_menu = require("./right_top_menu"); var _use_alert_bulk_actions = require("./use_alert_bulk_actions"); var _stateful_event_context = require("./stateful_event_context"); var _unit = require("../toolbar/unit"); var _use_get_field_spec = require("../../hooks/use_get_field_spec"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /* * 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. */ const storage = new _public.Storage(localStorage); const SECURITY_ALERTS_CONSUMERS = [_ruleDataUtils.AlertConsumers.SIEM]; /** * The stateful events viewer component is the highest level component that is utilized across the security_solution pages layer where * timeline is used BESIDES the flyout. The flyout makes use of the `EventsViewer` component which is a subcomponent here * NOTE: As of writting, it is not used in the Case_View component */ const StatefulEventsViewerComponent = ({ additionalFilters, additionalRightMenuOptions, bulkActions, cellActionsTriggerId, clearSelected, currentFilter, defaultModel, end, entityType = 'events', hasCrudPermissions = true, indexNames, leadingControlColumns, onRuleChange, pageFilters, renderCellValue, rowRenderers, setSelected, sourcererScope, start, tableId, unit = _unit.defaultUnit }) => { const dispatch = (0, _reactRedux.useDispatch)(); const theme = (0, _react.useContext)(_styledComponents.ThemeContext); const tableContext = (0, _react.useMemo)(() => ({ tableId }), [tableId]); const { filters, query, dataTable: { columns, defaultColumns, deletedEventIds, graphEventId, // If truthy, the graph viewer (Resolver) is showing itemsPerPage, itemsPerPageOptions, sessionViewConfig, showCheckboxes, sort, queryFields, selectAll, selectedEventIds, isSelectAllChecked, loadingEventIds, title } = defaultModel } = (0, _reactRedux.useSelector)(state => (0, _selectors.eventsViewerSelector)(state, tableId)); const { uiSettings, data, triggersActionsUi: { getFieldBrowser } } = (0, _kibana.useKibana)().services; const [tableView, setTableView] = (0, _react.useState)((0, _helpers.getDefaultViewSelection)({ tableId, value: storage.get(_constants.ALERTS_TABLE_VIEW_SELECTION_KEY) })); const { browserFields, dataViewId, indexPattern, runtimeMappings, selectedPatterns, dataViewId: selectedDataViewId, loading: isLoadingIndexPattern } = (0, _sourcerer.useSourcererDataView)(sourcererScope); const getFieldSpec = (0, _use_get_field_spec.useGetFieldSpec)(sourcererScope); const { globalFullScreen } = (0, _use_full_screen.useGlobalFullScreen)(); const editorActionsRef = (0, _react.useRef)(null); (0, _react.useEffect)(() => { dispatch(_securitysolutionDataTable.dataTableActions.createDataTable({ columns, dataViewId: selectedDataViewId, defaultColumns, id: tableId, indexNames: indexNames !== null && indexNames !== void 0 ? indexNames : selectedPatterns, itemsPerPage, showCheckboxes, sort })); return () => { dispatch(_actions.inputsActions.deleteOneQuery({ id: tableId, inputId: _constants2.InputsModelId.global })); if (editorActionsRef.current) { // eslint-disable-next-line react-hooks/exhaustive-deps editorActionsRef.current.closeEditor(); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const globalFilters = (0, _react.useMemo)(() => [...filters, ...(pageFilters !== null && pageFilters !== void 0 ? pageFilters : [])], [filters, pageFilters]); const { Navigation } = (0, _use_session_view.useSessionViewNavigation)({ scopeId: tableId }); const { DetailsPanel, SessionView } = (0, _use_session_view.useSessionView)({ entityType, scopeId: tableId }); const graphOverlay = (0, _react.useMemo)(() => { const shouldShowOverlay = graphEventId != null && graphEventId.length > 0 || sessionViewConfig != null; return shouldShowOverlay ? /*#__PURE__*/_react.default.createElement(_graph_overlay.GraphOverlay, { scopeId: tableId, SessionView: SessionView, Navigation: Navigation }) : null; }, [graphEventId, tableId, sessionViewConfig, SessionView, Navigation]); const setQuery = (0, _react.useCallback)(({ id, inspect, loading, refetch }) => dispatch(_actions.inputsActions.setQuery({ id, inputId: _constants2.InputsModelId.global, inspect, loading, refetch })), [dispatch]); const fieldBrowserOptions = (0, _fields_browser.useFieldBrowserOptions)({ sourcererScope, editorActionsRef, upsertColumn: (0, _react.useCallback)((column, index) => dispatch(_securitysolutionDataTable.dataTableActions.upsertColumn({ column, id: tableId, index })), [dispatch, tableId]), removeColumn: (0, _react.useCallback)(columnId => dispatch(_securitysolutionDataTable.dataTableActions.removeColumn({ columnId, id: tableId })), [dispatch, tableId]) }); const columnHeaders = (0, _lodash.isEmpty)(columns) ? _securitysolutionDataTable.defaultHeaders : columns; const esQueryConfig = (0, _common.getEsQueryConfig)(uiSettings); const filterQuery = (0, _react.useMemo)(() => (0, _helpers.getCombinedFilterQuery)({ config: esQueryConfig, browserFields, dataProviders: [], filters: globalFilters, from: start, indexPattern, kqlMode: 'filter', kqlQuery: query, to: end }), [esQueryConfig, browserFields, globalFilters, start, indexPattern, query, end]); const canQueryTimeline = (0, _react.useMemo)(() => filterQuery != null && isLoadingIndexPattern != null && !isLoadingIndexPattern && !(0, _lodash.isEmpty)(start) && !(0, _lodash.isEmpty)(end), [isLoadingIndexPattern, filterQuery, start, end]); const fields = (0, _react.useMemo)(() => [...columnHeaders.map(c => c.id), ...(queryFields !== null && queryFields !== void 0 ? queryFields : [])], [columnHeaders, queryFields]); const sortField = (0, _react.useMemo)(() => sort.map(({ columnId, columnType, esTypes, sortDirection }) => ({ field: columnId, type: columnType, direction: sortDirection, esTypes: esTypes !== null && esTypes !== void 0 ? esTypes : [] })), [sort]); const [loading, { events, loadPage, pageInfo, refetch, totalCount = 0, inspect }] = (0, _use_timelines_events.useTimelineEvents)({ // We rely on entityType to determine Events vs Alerts alertConsumers: SECURITY_ALERTS_CONSUMERS, data, dataViewId, endDate: end, entityType, fields, filterQuery, id: tableId, indexNames: indexNames !== null && indexNames !== void 0 ? indexNames : selectedPatterns, limit: itemsPerPage, runtimeMappings, skip: !canQueryTimeline, sort: sortField, startDate: start, filterStatus: currentFilter }); (0, _react.useEffect)(() => { dispatch(_securitysolutionDataTable.dataTableActions.updateIsLoading({ id: tableId, isLoading: loading })); }, [dispatch, tableId, loading]); const deleteQuery = (0, _react.useCallback)(({ id }) => dispatch(_actions.inputsActions.deleteOneQuery({ inputId: _constants2.InputsModelId.global, id })), [dispatch]); (0, _manage_query.useQueryInspector)({ queryId: tableId, loading, refetch, setQuery, deleteQuery, inspect }); const totalCountMinusDeleted = (0, _react.useMemo)(() => totalCount > 0 ? totalCount - deletedEventIds.length : 0, [deletedEventIds.length, totalCount]); const hasAlerts = totalCountMinusDeleted > 0; // Only show the table-spanning loading indicator when the query is loading and we // don't have data (e.g. for the initial fetch). // Subsequent fetches (e.g. for pagination) will show a small loading indicator on // top of the table and the table will display the current page until the next page // is fetched. This prevents a flicker when paginating. const showFullLoading = loading && !hasAlerts; const nonDeletedEvents = (0, _react.useMemo)(() => events.filter(e => !deletedEventIds.includes(e._id)), [deletedEventIds, events]); (0, _react.useEffect)(() => { setQuery({ id: tableId, inspect, loading, refetch }); }, [inspect, loading, refetch, setQuery, tableId]); // Clear checkbox selection when new events are fetched (0, _react.useEffect)(() => { dispatch(_securitysolutionDataTable.dataTableActions.clearSelected({ id: tableId })); dispatch(_securitysolutionDataTable.dataTableActions.setDataTableSelectAll({ id: tableId, selectAll: false })); }, [nonDeletedEvents, dispatch, tableId]); const onChangeItemsPerPage = (0, _react.useCallback)(itemsChangedPerPage => { dispatch(_securitysolutionDataTable.dataTableActions.updateItemsPerPage({ id: tableId, itemsPerPage: itemsChangedPerPage })); }, [tableId, dispatch]); const onChangePage = (0, _react.useCallback)(page => { loadPage(page); }, [loadPage]); const setEventsLoading = (0, _react.useCallback)(({ eventIds, isLoading }) => { dispatch(_securitysolutionDataTable.dataTableActions.setEventsLoading({ id: tableId, eventIds, isLoading })); }, [dispatch, tableId]); const setEventsDeleted = (0, _react.useCallback)(({ eventIds, isDeleted }) => { dispatch(_securitysolutionDataTable.dataTableActions.setEventsDeleted({ id: tableId, eventIds, isDeleted })); }, [dispatch, tableId]); const selectedCount = (0, _react.useMemo)(() => Object.keys(selectedEventIds).length, [selectedEventIds]); const onRowSelected = (0, _react.useCallback)(({ eventIds, isSelected }) => { setSelected({ id: tableId, eventIds: (0, _securitysolutionDataTable.getEventIdToDataMapping)(nonDeletedEvents, eventIds, queryFields, hasCrudPermissions), isSelected, isSelectAllChecked: isSelected && selectedCount + 1 === nonDeletedEvents.length }); }, [setSelected, tableId, nonDeletedEvents, queryFields, hasCrudPermissions, selectedCount]); const onSelectPage = (0, _react.useCallback)(({ isSelected }) => isSelected ? setSelected({ id: tableId, eventIds: (0, _securitysolutionDataTable.getEventIdToDataMapping)(nonDeletedEvents, nonDeletedEvents.map(event => event._id), queryFields, hasCrudPermissions), isSelected, isSelectAllChecked: isSelected }) : clearSelected({ id: tableId }), [setSelected, tableId, nonDeletedEvents, queryFields, hasCrudPermissions, clearSelected]); // Sync to selectAll so parent components can select all events (0, _react.useEffect)(() => { if (selectAll && !isSelectAllChecked) { onSelectPage({ isSelected: true }); } }, [isSelectAllChecked, onSelectPage, selectAll]); const [transformedLeadingControlColumns] = (0, _react.useMemo)(() => { return [showCheckboxes ? [_control_columns.checkBoxControlColumn, ...leadingControlColumns] : leadingControlColumns].map(controlColumns => (0, _control_columns.transformControlColumns)({ columnHeaders, controlColumns, data: nonDeletedEvents, fieldBrowserOptions, loadingEventIds, onRowSelected, onRuleChange, selectedEventIds, showCheckboxes, tabType: 'query', timelineId: tableId, isSelectAllChecked, sort, browserFields, onSelectPage, theme, setEventsLoading, setEventsDeleted, pageSize: itemsPerPage })); }, [showCheckboxes, leadingControlColumns, columnHeaders, nonDeletedEvents, fieldBrowserOptions, loadingEventIds, onRowSelected, onRuleChange, selectedEventIds, tableId, isSelectAllChecked, sort, browserFields, onSelectPage, theme, setEventsLoading, setEventsDeleted, itemsPerPage]); const alertBulkActions = (0, _use_alert_bulk_actions.useAlertBulkActions)({ tableId, data: nonDeletedEvents, totalItems: totalCountMinusDeleted, hasAlertsCrud: hasCrudPermissions, showCheckboxes, filterStatus: currentFilter, filterQuery, bulkActions, selectedCount }); // Store context in state rather than creating object in provider value={} to prevent re-renders caused by a new object being created const [activeStatefulEventContext] = (0, _react.useState)({ timelineID: tableId, tabType: 'query', enableHostDetailsFlyout: true, enableIpDetailsFlyout: true }); const unitCountText = (0, _react.useMemo)(() => `${totalCountMinusDeleted.toLocaleString()} ${unit(totalCountMinusDeleted)}`, [totalCountMinusDeleted, unit]); const rowHeightsOptions = (0, _react.useMemo)(() => { if (tableView === 'eventRenderedView') { return { defaultHeight: 'auto' }; } return undefined; }, [tableView]); const pagination = (0, _react.useMemo)(() => ({ pageIndex: pageInfo.activePage, pageSize: itemsPerPage, pageSizeOptions: itemsPerPageOptions, onChangeItemsPerPage, onChangePage }), [itemsPerPage, itemsPerPageOptions, onChangeItemsPerPage, onChangePage, pageInfo.activePage]); return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_styles.FullScreenContainer, { $isFullScreen: globalFullScreen }, /*#__PURE__*/_react.default.createElement(_inspect.InspectButtonContainer, null, /*#__PURE__*/_react.default.createElement(_styles.StyledEuiPanel, { hasBorder: false, hasShadow: false, paddingSize: "none", "data-test-subj": "events-viewer-panel", $isFullScreen: globalFullScreen }, showFullLoading && /*#__PURE__*/_react.default.createElement(_shared.TableLoading, { height: "short" }), graphOverlay, canQueryTimeline && /*#__PURE__*/_react.default.createElement(_shared.TableContext.Provider, { value: tableContext }, /*#__PURE__*/_react.default.createElement(_styles.EventsContainerLoading, { "data-timeline-id": tableId, "data-test-subj": `events-container-loading-${loading}` }, /*#__PURE__*/_react.default.createElement(_right_top_menu.RightTopMenu, { tableView: tableView, loading: loading, tableId: tableId, title: title, onViewChange: selectedView => setTableView(selectedView), additionalFilters: additionalFilters, hasRightOffset: tableView === 'gridView' && nonDeletedEvents.length > 0, additionalMenuOptions: additionalRightMenuOptions }), !hasAlerts && !loading && !graphOverlay && /*#__PURE__*/_react.default.createElement(_shared.EmptyTable, null), hasAlerts && /*#__PURE__*/_react.default.createElement(_styles.FullWidthFlexGroupTable, { $visible: !graphEventId && graphOverlay == null, gutterSize: "none" }, /*#__PURE__*/_react.default.createElement(_styles.ScrollableFlexItem, { grow: 1 }, /*#__PURE__*/_react.default.createElement(_stateful_event_context.StatefulEventContext.Provider, { value: activeStatefulEventContext }, /*#__PURE__*/_react.default.createElement(_securitysolutionDataTable.DataTableComponent, { cellActionsTriggerId: cellActionsTriggerId, additionalControls: alertBulkActions, unitCountText: unitCountText, browserFields: browserFields, data: nonDeletedEvents, id: tableId, loadPage: loadPage // TODO: migrate away from deprecated type , renderCellValue: renderCellValue // TODO: migrate away from deprecated type , rowRenderers: rowRenderers, totalItems: totalCountMinusDeleted, bulkActions: bulkActions, fieldBrowserOptions: fieldBrowserOptions, hasCrudPermissions: hasCrudPermissions, leadingControlColumns: transformedLeadingControlColumns, pagination: pagination, isEventRenderedView: tableView === 'eventRenderedView', rowHeightsOptions: rowHeightsOptions, getFieldBrowser: getFieldBrowser, getFieldSpec: getFieldSpec }))))))))), DetailsPanel); }; const mapDispatchToProps = { clearSelected: _securitysolutionDataTable.dataTableActions.clearSelected, setSelected: _securitysolutionDataTable.dataTableActions.setSelected }; const connector = (0, _reactRedux.connect)(undefined, mapDispatchToProps); const StatefulEventsViewer = connector(StatefulEventsViewerComponent); exports.StatefulEventsViewer = StatefulEventsViewer;