"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.collapseProcessTree = exports.buildProcessTree = exports.autoExpandProcessTree = void 0; exports.inferProcessFromLeaderInfo = inferProcessFromLeaderInfo; exports.updateProcessMap = exports.updateAlertEventStatus = exports.searchProcessTree = exports.processNewEvents = void 0; var _uuid = require("uuid"); var _lodash = require("lodash"); var _sort_processes = require("../../../common/utils/sort_processes"); var _hooks = require("./hooks"); /* * 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. */ // Creates an instance of Process, from a nested leader process fieldset // This is used to ensure we always have a record for a session leader, as well as // a parent record for potentially orphaned processes function inferProcessFromLeaderInfo(sourceEvent, leader) { const entityId = (leader === null || leader === void 0 ? void 0 : leader.entity_id) || (0, _uuid.v4)(); const process = new _hooks.ProcessImpl(entityId); if (sourceEvent && leader) { var _sourceEvent$process; const event = { ...sourceEvent, process: { ...sourceEvent.process, ...leader }, user: leader.user, group: leader.group, event: { ...sourceEvent.event, id: `fake-${entityId}` } }; // won't be accurate, so removing if (((_sourceEvent$process = sourceEvent.process) === null || _sourceEvent$process === void 0 ? void 0 : _sourceEvent$process.parent) === leader) { var _event$process; (_event$process = event.process) === null || _event$process === void 0 ? true : delete _event$process.parent; } process.addEvent(event); } return process; } // if given event is an alert, and it exist in updatedAlertsStatus, update the alert's status // with the updated status value in updatedAlertsStatus Map const updateAlertEventStatus = (events, updatedAlertsStatus) => events.map(event => { var _updatedAlertsStatus$, _updatedAlertsStatus, _event$kibana$alert$u, _event$kibana$alert, _event$kibana$alert2; // do nothing if event is not an alert if (!event.kibana) { return event; } return { ...event, kibana: { ...event.kibana, alert: { ...event.kibana.alert, workflow_status: (_updatedAlertsStatus$ = (_updatedAlertsStatus = updatedAlertsStatus[(_event$kibana$alert$u = (_event$kibana$alert = event.kibana.alert) === null || _event$kibana$alert === void 0 ? void 0 : _event$kibana$alert.uuid) !== null && _event$kibana$alert$u !== void 0 ? _event$kibana$alert$u : '']) === null || _updatedAlertsStatus === void 0 ? void 0 : _updatedAlertsStatus.status) !== null && _updatedAlertsStatus$ !== void 0 ? _updatedAlertsStatus$ : (_event$kibana$alert2 = event.kibana.alert) === null || _event$kibana$alert2 === void 0 ? void 0 : _event$kibana$alert2.workflow_status } } }; }); // given a page of new events, add these events to the appropriate process class model // create a new process if none are created and return the mutated processMap exports.updateAlertEventStatus = updateAlertEventStatus; const updateProcessMap = (processMap, events) => { events.forEach(event => { var _event$process2, _event$kibana, _event$event; const { entity_id: id } = (_event$process2 = event.process) !== null && _event$process2 !== void 0 ? _event$process2 : {}; if (!id) { return; } let process = processMap[id]; if (!process) { process = new _hooks.ProcessImpl(id); processMap[id] = process; } if ((_event$kibana = event.kibana) !== null && _event$kibana !== void 0 && _event$kibana.alert) { process.addAlert(event); } else if (((_event$event = event.event) === null || _event$event === void 0 ? void 0 : _event$event.kind) === 'event') { process.addEvent(event); } }); return processMap; }; // given a page of events, update process model parent child relationships // if we cannot find a parent for a process include said process // in the array of orphans. We track orphans in their own array, so // we can attempt to re-parent the orphans when new pages of events are // processed. This is especially important when paginating backwards // (e.g in the case where the SessionView jumpToEvent prop is used, potentially skipping over ancestor processes) exports.updateProcessMap = updateProcessMap; const buildProcessTree = (processMap, events, orphans, sessionEntityId, backwardDirection = false) => { // we process events in reverse order when paginating backwards. if (backwardDirection) { events = events.slice().reverse(); } events.forEach(event => { var _event$process3, _parent$entity_id; const { entity_id: id, parent } = (_event$process3 = event.process) !== null && _event$process3 !== void 0 ? _event$process3 : {}; const process = processMap[id !== null && id !== void 0 ? id : '']; let parentProcess = processMap[(_parent$entity_id = parent === null || parent === void 0 ? void 0 : parent.entity_id) !== null && _parent$entity_id !== void 0 ? _parent$entity_id : '']; // if either entity_id or parent does not exist, return // if process already has a parent, return if (!id || !parent || process.parent || id === sessionEntityId) { return; } if (!parentProcess) { var _event$process4; // infer a fake process for the parent, incase we don't end up loading any parent events (due to filtering or jumpToCursor pagination) const parentFields = event === null || event === void 0 ? void 0 : (_event$process4 = event.process) === null || _event$process4 === void 0 ? void 0 : _event$process4.parent; if (parentFields !== null && parentFields !== void 0 && parentFields.entity_id && !processMap[parentFields.entity_id]) { parentProcess = inferProcessFromLeaderInfo(event, parentFields); processMap[parentProcess.id] = parentProcess; if (!orphans.includes(parentProcess)) { orphans.push(parentProcess); } } else { if (!orphans.includes(process)) { orphans.push(process); } } } if (parentProcess) { process.parent = parentProcess; // handy for recursive operations (like auto expand) parentProcess.addChild(process); } }); const newOrphans = []; // with this new page of events processed, lets try re-parent any orphans orphans === null || orphans === void 0 ? void 0 : orphans.forEach(process => { var _process$getDetails$p, _process$getDetails$p2; const parentProcessId = (_process$getDetails$p = process.getDetails().process) === null || _process$getDetails$p === void 0 ? void 0 : (_process$getDetails$p2 = _process$getDetails$p.parent) === null || _process$getDetails$p2 === void 0 ? void 0 : _process$getDetails$p2.entity_id; if (parentProcessId) { const parentProcess = processMap[parentProcessId]; if (parentProcess) { process.parent = parentProcess; parentProcess.addChild(process); return; } } newOrphans.push(process); }); return newOrphans; }; // given a plain text searchQuery, iterates over all processes in processMap // and marks ones which match the below text (currently what is rendered in the process line item) // process.searchMatched is used by process_tree_node to highlight the text which matched the search // this funtion also returns a list of process results which is used by session_view_search_bar to drive // result navigation UX // FYI: this function mutates properties of models contained in processMap exports.buildProcessTree = buildProcessTree; const searchProcessTree = (processMap, searchQuery, verboseMode) => { const results = []; for (const processId of Object.keys(processMap)) { const process = processMap[processId]; if (searchQuery) { var _details$process, _event$process5; const details = process.getDetails(); const entryLeader = details === null || details === void 0 ? void 0 : (_details$process = details.process) === null || _details$process === void 0 ? void 0 : _details$process.entry_leader; // if this is the entry leader process OR verbose mode is OFF and is a verbose process, don't match. if ((entryLeader === null || entryLeader === void 0 ? void 0 : entryLeader.entity_id) === process.id || !verboseMode && process.isVerbose()) { continue; } const event = process.getDetails(); const { working_directory: workingDirectory, args } = (_event$process5 = event.process) !== null && _event$process5 !== void 0 ? _event$process5 : {}; // TODO: the text we search is the same as what we render. // in future we may support KQL searches to match against any property // for now plain text search is limited to searching process.working_directory + process.args const text = `${workingDirectory !== null && workingDirectory !== void 0 ? workingDirectory : ''} ${args === null || args === void 0 ? void 0 : args.join(' ')}`; const searchMatch = [...text.matchAll(new RegExp((0, _lodash.escapeRegExp)(searchQuery), 'gi'))]; process.searchMatched = searchMatch.length > 0 ? getSearchMatchedIndices(text, searchMatch) : null; if (process.searchMatched) { results.push(process); } } else { process.clearSearch(); } } return results.sort(_sort_processes.sortProcesses); }; exports.searchProcessTree = searchProcessTree; const getSearchMatchedIndices = (text, matches) => { return text.split('').reduce((accum, _, idx) => { const findMatch = matches.find(match => match.index !== undefined && idx >= match.index && idx < match.index + match[0].length); if (findMatch) { accum = [...accum, idx]; } return accum; }, []); }; // Iterate over all processes in processMap, and mark each process (and it's ancestors) for auto expansion if: // a) the process was "user entered" (aka an interactive group leader) // b) we are jumping to a specific process // Returns the processMap with it's processes autoExpand bool set to true or false // process.autoExpand is read by process_tree_node to determine whether to auto expand it's child processes. const autoExpandProcessTree = (processMap, jumpToEntityId) => { for (const processId of Object.keys(processMap)) { const process = processMap[processId]; if (process.isUserEntered() || jumpToEntityId === process.id || process.hasAlerts()) { let { parent } = process; const parentIdSet = new Set(); while (parent && !parentIdSet.has(parent.id)) { parentIdSet.add(parent.id); parent.autoExpand = true; parent = parent.parent; } } } return processMap; }; // recusively collapses all children below provided node exports.autoExpandProcessTree = autoExpandProcessTree; const collapseProcessTree = node => { if (!node.autoExpand) { return; } if (node.children) { node.children.forEach(child => { child.autoExpand = false; collapseProcessTree(child); }); } }; exports.collapseProcessTree = collapseProcessTree; const processNewEvents = (eventsProcessMap, events, orphans, sessionEntityId, backwardDirection = false) => { if (!events || events.length === 0) { return [eventsProcessMap, orphans]; } const updatedProcessMap = updateProcessMap(eventsProcessMap, events); const newOrphans = buildProcessTree(updatedProcessMap, events, orphans, sessionEntityId, backwardDirection); return [autoExpandProcessTree(updatedProcessMap), newOrphans]; }; exports.processNewEvents = processNewEvents;