diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/alerts_cases.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/alerts_cases.cy.ts index 1b3cd0541580b..45e0228edaa10 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/alerts_cases.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/alerts_cases.cy.ts @@ -25,8 +25,7 @@ import { } from '../../tasks/live_query'; import { generateRandomStringName, interceptCaseId } from '../../tasks/integrations'; -// Failing: See https://github.com/elastic/kibana/issues/197151 -describe.skip( +describe( 'Alert Event Details - Cases', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => { @@ -107,7 +106,7 @@ describe.skip( cy.getBySel('securitySolutionFlyoutResponseButton').click(); cy.getBySel('responseActionsViewWrapper').should('exist'); cy.contains('select * from users;'); - cy.contains("SELECT * FROM os_version where name='Ubuntu';"); + cy.contains(/SELECT \* FROM os_version where name='.+'/); cy.getBySel('osquery-results-comment').each(($comment) => { cy.wrap($comment).within(() => { // On initial load result table might not render due to displayed error diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/alerts_linked_apps.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/alerts_linked_apps.cy.ts index 9d248c8178b6b..973ec2f2f4308 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/alerts_linked_apps.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/alerts_linked_apps.cy.ts @@ -17,8 +17,7 @@ import { } from '../../tasks/live_query'; import { closeModalIfVisible, closeToastIfVisible } from '../../tasks/integrations'; -// FLAKY: https://github.com/elastic/kibana/issues/218206 -describe.skip( +describe( 'Alert Event Details', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'], diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/cases.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/cases.cy.ts index 416151caac342..00047913118c6 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/cases.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/cases.cy.ts @@ -46,7 +46,7 @@ describe('Add to Cases', () => { cleanupCase(caseId); }); - it('should add result a case and not have add to timeline in result', () => { + it('should add result to a case without showing add to timeline button', () => { addLiveQueryToCase(liveQueryId, caseId); cy.contains(`Case ${caseTitle} updated`); viewRecentCaseAndCheckResults(); @@ -78,7 +78,7 @@ describe('Add to Cases', () => { cleanupCase(caseId); }); - it('should add result a case and have add to timeline in result', () => { + it('should add result to a case without showing add to timeline button', () => { addLiveQueryToCase(liveQueryId, caseId); cy.contains(`Case ${caseTitle} updated`); viewRecentCaseAndCheckResults(); @@ -88,7 +88,7 @@ describe('Add to Cases', () => { lens: true, discover: true, cases: false, - timeline: true, + timeline: false, }); }); }); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/timelines.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/timelines.cy.ts index 02e6d02d474bc..00a200d3f3262 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/timelines.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/timelines.cy.ts @@ -10,8 +10,7 @@ import { takeOsqueryActionWithParams } from '../../tasks/live_query'; import { ServerlessRoleName } from '../../support/roles'; import { disableNewFeaturesTours } from '../../tasks/navigation'; -// FLAKY: https://github.com/elastic/kibana/issues/229432 -describe.skip('ALL - Timelines', { tags: ['@ess'] }, () => { +describe('ALL - Timelines', { tags: ['@ess'] }, () => { before(() => { initializeDataViews(); }); diff --git a/x-pack/platform/plugins/shared/osquery/moon.yml b/x-pack/platform/plugins/shared/osquery/moon.yml index 4761def8c5e57..20dc6b872f870 100644 --- a/x-pack/platform/plugins/shared/osquery/moon.yml +++ b/x-pack/platform/plugins/shared/osquery/moon.yml @@ -36,7 +36,6 @@ dependsOn: - '@kbn/lens-plugin' - '@kbn/security-plugin' - '@kbn/triggers-actions-ui-plugin' - - '@kbn/timelines-plugin' - '@kbn/spaces-plugin' - '@kbn/i18n' - '@kbn/rison' diff --git a/x-pack/platform/plugins/shared/osquery/public/live_queries/form/index.tsx b/x-pack/platform/plugins/shared/osquery/public/live_queries/form/index.tsx index 6b99e6228be49..85aefed836918 100644 --- a/x-pack/platform/plugins/shared/osquery/public/live_queries/form/index.tsx +++ b/x-pack/platform/plugins/shared/osquery/public/live_queries/form/index.tsx @@ -26,6 +26,7 @@ import { usePacks } from '../../packs/use_packs'; import { useCreateLiveQuery } from '../use_create_live_query_action'; import { useLiveQueryDetails } from '../../actions/use_live_query_details'; import type { AgentSelection } from '../../agents/types'; +import type { AddToTimelineHandler } from '../../types'; import LiveQueryQueryField from './live_query_query_field'; import { AgentsTableField } from './agents_table_field'; import { savedQueryDataSerializer } from '../../saved_queries/form/use_saved_query_form'; @@ -63,6 +64,7 @@ interface LiveQueryFormProps { formType?: FormType; enabled?: boolean; hideAgentsField?: boolean; + addToTimeline?: AddToTimelineHandler; } const LiveQueryFormComponent: React.FC = ({ @@ -72,6 +74,7 @@ const LiveQueryFormComponent: React.FC = ({ formType = 'steps', enabled = true, hideAgentsField = false, + addToTimeline, }) => { const alertAttachmentContext = useContext(AlertAttachmentContext); @@ -228,9 +231,11 @@ const LiveQueryFormComponent: React.FC = ({ endDate={singleQueryDetails?.expiration} agentIds={singleQueryDetails?.agents} liveQueryActionId={liveQueryActionId} + addToTimeline={addToTimeline} /> ) : null, [ + addToTimeline, singleQueryDetails?.action_id, singleQueryDetails?.expiration, singleQueryDetails?.agents, @@ -316,6 +321,7 @@ const LiveQueryFormComponent: React.FC = ({ liveQueryDetails={liveQueryDetails} submitButtonContent={submitButtonContent} showResultsHeader + addToTimeline={addToTimeline} /> ) : ( <> diff --git a/x-pack/platform/plugins/shared/osquery/public/live_queries/form/pack_queries_status_table.tsx b/x-pack/platform/plugins/shared/osquery/public/live_queries/form/pack_queries_status_table.tsx index fdc2c1b25df68..48559e02ef587 100644 --- a/x-pack/platform/plugins/shared/osquery/public/live_queries/form/pack_queries_status_table.tsx +++ b/x-pack/platform/plugins/shared/osquery/public/live_queries/form/pack_queries_status_table.tsx @@ -32,6 +32,7 @@ import { PackViewInLensAction } from '../../lens/pack_view_in_lens'; import { PackViewInDiscoverAction } from '../../discover/pack_view_in_discover'; import { AddToCaseWrapper } from '../../cases/add_to_cases'; import { AddToTimelineButton } from '../../timelines/add_to_timeline_button'; +import type { AddToTimelineHandler } from '../../types'; const truncateTooltipTextCss = { width: '100%', @@ -140,6 +141,7 @@ interface PackQueriesStatusTableProps { startDate?: string; expirationDate?: string; showResultsHeader?: boolean; + addToTimeline?: AddToTimelineHandler; } const PackQueriesStatusTableComponent: React.FC = ({ @@ -150,6 +152,7 @@ const PackQueriesStatusTableComponent: React.FC = ( startDate, expirationDate, showResultsHeader, + addToTimeline, }) => { const [queryDetailsFlyoutOpen, setQueryDetailsFlyoutOpen] = useState<{ id: string; @@ -242,6 +245,7 @@ const PackQueriesStatusTableComponent: React.FC = ( agentIds={agentIds} failedAgentsCount={item?.failed ?? 0} error={item.error} + addToTimeline={addToTimeline} /> @@ -251,7 +255,7 @@ const PackQueriesStatusTableComponent: React.FC = ( return itemIdToExpandedRowMapValues; }); }, - [actionId, startDate, expirationDate, agentIds] + [actionId, startDate, expirationDate, agentIds, addToTimeline] ); const renderToggleResultsAction = useCallback( @@ -285,7 +289,12 @@ const PackQueriesStatusTableComponent: React.FC = ( { render: (item: { action_id: string }) => item.action_id && ( - + ), }, { @@ -317,6 +326,7 @@ const PackQueriesStatusTableComponent: React.FC = ( }, [ actionId, + addToTimeline, agentIds, handleQueryFlyoutOpen, renderDiscoverResultsAction, @@ -422,6 +432,7 @@ const PackQueriesStatusTableComponent: React.FC = ( queryIds={queryIds as string[]} actionId={actionId} agentIds={agentIds} + addToTimeline={addToTimeline} /> )} ({ @@ -29,8 +31,8 @@ const iconsListCss = { }; export const PackResultsHeader = React.memo( - ({ actionId, agentIds, queryIds }) => { - const iconProps = useMemo(() => ({ color: 'text', size: 'xs', iconSize: 'l' }), []); + ({ actionId, agentIds, queryIds, addToTimeline }) => { + const iconProps = useMemo(() => ({ color: 'text', size: 'xs', iconSize: 'l' } as const), []); return ( <> @@ -64,6 +66,7 @@ export const PackResultsHeader = React.memo( value={queryIds} isIcon={true} iconProps={iconProps} + addToTimeline={addToTimeline} /> diff --git a/x-pack/platform/plugins/shared/osquery/public/live_queries/index.tsx b/x-pack/platform/plugins/shared/osquery/public/live_queries/index.tsx index c6738cc99b3df..6146bddeb2ae7 100644 --- a/x-pack/platform/plugins/shared/osquery/public/live_queries/index.tsx +++ b/x-pack/platform/plugins/shared/osquery/public/live_queries/index.tsx @@ -13,6 +13,7 @@ import { replaceParamsQuery } from '../../common/utils/replace_params_query'; import { AlertAttachmentContext } from '../common/contexts'; import { LiveQueryForm } from './form'; import type { AgentSelection } from '../agents/types'; +import type { AddToTimelineHandler } from '../types'; interface LiveQueryProps { agentId?: string; @@ -32,6 +33,7 @@ interface LiveQueryProps { hideAgentsField?: boolean; packId?: string; agentSelection?: AgentSelection; + addToTimeline?: AddToTimelineHandler; } const LiveQueryComponent: React.FC = ({ @@ -51,6 +53,7 @@ const LiveQueryComponent: React.FC = ({ packId, agentSelection, timeout, + addToTimeline, }) => { const initialAgentSelection = useMemo(() => { if (agentSelection) { @@ -103,6 +106,7 @@ const LiveQueryComponent: React.FC = ({ formType={formType} enabled={enabled} hideAgentsField={hideAgentsField} + addToTimeline={addToTimeline} /> ); }; diff --git a/x-pack/platform/plugins/shared/osquery/public/results/results_table.tsx b/x-pack/platform/plugins/shared/osquery/public/results/results_table.tsx index 56fe0e12da113..c01da5edeff55 100644 --- a/x-pack/platform/plugins/shared/osquery/public/results/results_table.tsx +++ b/x-pack/platform/plugins/shared/osquery/public/results/results_table.tsx @@ -30,6 +30,7 @@ import React, { createContext, useEffect, useState, useCallback, useContext, use import type { ECSMapping } from '@kbn/osquery-io-ts-types'; import { pagePathGetters } from '@kbn/fleet-plugin/public'; import { AddToTimelineButton } from '../timelines/add_to_timeline_button'; +import type { AddToTimelineHandler } from '../types'; import { useAllResults } from './use_all_results'; import type { ResultEdges } from '../../common/search_strategy'; import { Direction } from '../../common/search_strategy'; @@ -99,6 +100,7 @@ export interface ResultsTableComponentProps { startDate?: string; liveQueryActionId?: string; error?: string; + addToTimeline?: AddToTimelineHandler; } const ResultsTableComponent: React.FC = ({ @@ -109,6 +111,7 @@ const ResultsTableComponent: React.FC = ({ endDate, liveQueryActionId, error, + addToTimeline, }) => { const [isLive, setIsLive] = useState(true); @@ -126,7 +129,6 @@ const ResultsTableComponent: React.FC = ({ const { application: { getUrlForApp }, appName, - timelines, notifications: { toasts }, i18n: i18nStart, theme, @@ -369,7 +371,7 @@ const ResultsTableComponent: React.FC = ({ const leadingControlColumns: EuiDataGridControlColumn[] = useMemo(() => { const edges = allResultsData?.edges; - if (timelines && edges) { + if (addToTimeline && edges) { return [ { id: 'timeline', @@ -381,14 +383,21 @@ const ResultsTableComponent: React.FC = ({ }; const eventId = edges[visibleRowIndex]?._id; - return ; + return ( + + ); }, }, ]; } return []; - }, [allResultsData?.edges, timelines]); + }, [addToTimeline, allResultsData?.edges]); const toolbarVisibility = useMemo( () => ({ @@ -408,14 +417,14 @@ const ResultsTableComponent: React.FC = ({ endDate={endDate} startDate={startDate} /> - + {liveQueryActionId && ( )} ), }), - [actionId, agentIds, appName, endDate, liveQueryActionId, startDate] + [actionId, addToTimeline, agentIds, appName, endDate, liveQueryActionId, startDate] ); useEffect( diff --git a/x-pack/platform/plugins/shared/osquery/public/routes/saved_queries/edit/tabs.tsx b/x-pack/platform/plugins/shared/osquery/public/routes/saved_queries/edit/tabs.tsx index c55bebcd9056b..c19511fc94efa 100644 --- a/x-pack/platform/plugins/shared/osquery/public/routes/saved_queries/edit/tabs.tsx +++ b/x-pack/platform/plugins/shared/osquery/public/routes/saved_queries/edit/tabs.tsx @@ -11,6 +11,7 @@ import type { ECSMapping } from '@kbn/osquery-io-ts-types'; import { ResultsTable } from '../../../results/results_table'; import { ActionResultsSummary } from '../../../action_results/action_results_summary'; +import type { AddToTimelineHandler } from '../../../types'; const euiTabbedContentCss = { 'div.euiTabs': { @@ -27,6 +28,7 @@ interface ResultTabsProps { endDate?: string; liveQueryActionId?: string; error?: string; + addToTimeline?: AddToTimelineHandler; } const ResultTabsComponent: React.FC = ({ @@ -38,6 +40,7 @@ const ResultTabsComponent: React.FC = ({ startDate, liveQueryActionId, error, + addToTimeline, }) => { const tabs = useMemo( () => [ @@ -54,6 +57,7 @@ const ResultTabsComponent: React.FC = ({ endDate={endDate} liveQueryActionId={liveQueryActionId} error={error} + addToTimeline={addToTimeline} /> ), }, @@ -86,6 +90,7 @@ const ResultTabsComponent: React.FC = ({ liveQueryActionId, error, failedAgentsCount, + addToTimeline, ] ); diff --git a/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_action/index.tsx b/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_action/index.tsx index 1d4a8137efe73..1099925dc0ba9 100644 --- a/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_action/index.tsx +++ b/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_action/index.tsx @@ -15,6 +15,7 @@ import { useKibana } from '../../common/lib/kibana'; import { LiveQuery } from '../../live_queries'; import { OsqueryIcon } from '../../components/osquery_icon'; import { useIsOsqueryAvailable } from '../use_is_osquery_available'; +import type { AddToTimelineHandler } from '../../types'; export interface OsqueryActionProps { agentId?: string; @@ -22,6 +23,7 @@ export interface OsqueryActionProps { formType: 'steps' | 'simple'; hideAgentsField?: boolean; onSuccess?: () => void; + addToTimeline?: AddToTimelineHandler; } const OsqueryActionComponent: React.FC = ({ @@ -30,6 +32,7 @@ const OsqueryActionComponent: React.FC = ({ defaultValues, hideAgentsField, onSuccess, + addToTimeline, }) => { const permissions = useKibana().services.application.capabilities.osquery; @@ -94,6 +97,7 @@ const OsqueryActionComponent: React.FC = ({ agentId={agentId} hideAgentsField={hideAgentsField} onSuccess={onSuccess} + addToTimeline={addToTimeline} {...defaultValues} /> ); diff --git a/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_response_action_type/pack_field_wrapper.tsx b/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_response_action_type/pack_field_wrapper.tsx index 4942439844bf7..a5c3a259c4170 100644 --- a/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_response_action_type/pack_field_wrapper.tsx +++ b/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_response_action_type/pack_field_wrapper.tsx @@ -13,6 +13,7 @@ import type { ECSMapping } from '@kbn/osquery-io-ts-types'; import { PackQueriesStatusTable } from '../../live_queries/form/pack_queries_status_table'; import { usePacks } from '../../packs/use_packs'; import { PacksComboBoxField } from '../../live_queries/form/packs_combobox_field'; +import type { AddToTimelineHandler } from '../../types'; interface PackFieldWrapperProps { liveQueryDetails?: { @@ -26,12 +27,14 @@ interface PackFieldWrapperProps { }; submitButtonContent?: React.ReactNode; showResultsHeader?: boolean; + addToTimeline?: AddToTimelineHandler; } export const PackFieldWrapper = ({ liveQueryDetails, submitButtonContent, showResultsHeader, + addToTimeline, }: PackFieldWrapperProps) => { const { data: packsData } = usePacks({}); const { packId } = useWatch<{ packId: string[] }>(); @@ -64,6 +67,7 @@ export const PackFieldWrapper = ({ // @ts-expect-error update types data={liveQueryDetails?.queries ?? selectedPackData?.queries} showResultsHeader={showResultsHeader} + addToTimeline={addToTimeline} /> ) : null} diff --git a/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/osquery_result.tsx b/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/osquery_result.tsx index 30e166ae67ad9..8abafd24bc2bd 100644 --- a/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/osquery_result.tsx +++ b/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/osquery_result.tsx @@ -23,7 +23,7 @@ interface OsqueryResultProps extends OsqueryActionResultsProps { // eslint-disable-next-line react/display-name export const OsqueryResult = React.memo( - ({ actionId, ruleName, startDate, ecsData }) => { + ({ actionId, ruleName, startDate, ecsData, addToTimeline }) => { const [isLive, setIsLive] = useState(false); const { data } = useLiveQueryDetails({ actionId, @@ -49,6 +49,7 @@ export const OsqueryResult = React.memo( startDate={data?.['@timestamp']} expirationDate={data?.expiration} agentIds={data?.agents} + addToTimeline={addToTimeline} /> diff --git a/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/osquery_result_wrapper.tsx b/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/osquery_result_wrapper.tsx index 7edf003324a60..49c02fdcdc187 100644 --- a/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/osquery_result_wrapper.tsx +++ b/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/osquery_result_wrapper.tsx @@ -25,7 +25,7 @@ import type { OsqueryActionResultProps } from './types'; // eslint-disable-next-line react/display-name const OsqueryResultComponent = React.memo( - ({ actionId, ruleName, startDate, ecsData }) => { + ({ actionId, ruleName, startDate, ecsData, addToTimeline }) => { const { read } = useKibana().services.application.capabilities.osquery; const [isLive, setIsLive] = useState(false); @@ -56,6 +56,7 @@ const OsqueryResultComponent = React.memo( startDate={data?.['@timestamp']} expirationDate={data?.expiration} agentIds={data?.agents} + addToTimeline={addToTimeline} /> )} diff --git a/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/osquery_results.tsx b/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/osquery_results.tsx index b0f4996bd0b0a..6eeceb9c53abe 100644 --- a/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/osquery_results.tsx +++ b/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/osquery_results.tsx @@ -23,6 +23,7 @@ const OsqueryActionResultsComponent: React.FC = ({ ruleName, actionItems, ecsData, + addToTimeline, }) => { const { read } = useKibana().services.application.capabilities.osquery; @@ -43,6 +44,7 @@ const OsqueryActionResultsComponent: React.FC = ({ startDate={startDate} ruleName={ruleName} ecsData={ecsData} + addToTimeline={addToTimeline} /> ); })} diff --git a/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/types.ts b/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/types.ts index 41f6fbb0e6e23..846017a117c39 100644 --- a/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/types.ts +++ b/x-pack/platform/plugins/shared/osquery/public/shared_components/osquery_results/types.ts @@ -7,11 +7,13 @@ import type { Ecs } from '@kbn/cases-plugin/common'; import type { ActionEdges } from '../../../common/search_strategy'; +import type { AddToTimelineHandler } from '../../types'; export interface OsqueryActionResultsProps { ruleName?: string; ecsData?: Ecs | null; actionItems?: ActionEdges; + addToTimeline?: AddToTimelineHandler; } export interface OsqueryActionResultProps { @@ -19,4 +21,5 @@ export interface OsqueryActionResultProps { ecsData?: Ecs | null; actionId: string; startDate: string; + addToTimeline?: AddToTimelineHandler; } diff --git a/x-pack/platform/plugins/shared/osquery/public/timelines/add_to_timeline_button.tsx b/x-pack/platform/plugins/shared/osquery/public/timelines/add_to_timeline_button.tsx index f59ccfee00abb..dd281fa811f6f 100644 --- a/x-pack/platform/plugins/shared/osquery/public/timelines/add_to_timeline_button.tsx +++ b/x-pack/platform/plugins/shared/osquery/public/timelines/add_to_timeline_button.tsx @@ -5,62 +5,79 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { isArray } from 'lodash'; -import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui'; +import { EuiButtonEmpty, EuiButtonIcon, type EuiButtonIconProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { useKibana } from '../common/lib/kibana'; - -const TimelineComponent = React.memo((props) => ); -TimelineComponent.displayName = 'TimelineComponent'; +import type { AddToTimelineHandler, OsqueryDataProvider } from '../types'; export interface AddToTimelineButtonProps { field: string; value: string | string[]; + addToTimeline?: AddToTimelineHandler; isIcon?: true; - iconProps?: Record; + iconProps?: Partial; } export const SECURITY_APP_NAME = 'Security'; + export const AddToTimelineButton = (props: AddToTimelineButtonProps) => { - const { timelines, appName, analytics, i18n, theme } = useKibana().services; - const startServices = { analytics, i18n, theme }; - const { field, value, isIcon, iconProps } = props; + const { appName } = useKibana().services; + const { field, value, isIcon, iconProps, addToTimeline } = props; + const addToTimelineLabel = i18n.translate('xpack.osquery.addToTimelineButtonLabel', { + defaultMessage: 'Add to Timeline investigation', + }); - const queryIds = isArray(value) ? value : [value]; - const TimelineIconComponent = useCallback( - (timelineComponentProps: any) => ( - - ), - [iconProps] + const queryIds = useMemo(() => (isArray(value) ? value : [value]), [value]); + + const providers: OsqueryDataProvider[] = useMemo( + () => + queryIds.map((queryId) => ({ + and: [], + enabled: true, + excluded: false, + id: queryId, + kqlQuery: '', + name: queryId, + queryMatch: { + field, + value: queryId, + operator: ':' as const, + }, + })), + [field, queryIds] ); - if (!timelines || appName !== SECURITY_APP_NAME || !queryIds.length) { + const handleClick = useCallback(() => { + addToTimeline?.(providers); + }, [addToTimeline, providers]); + + if (appName !== SECURITY_APP_NAME || !queryIds.length || !addToTimeline) { return null; } - const { getAddToTimelineButton } = timelines.getHoverActions(); - - const providers = queryIds.map((queryId) => ({ - and: [], - enabled: true, - excluded: false, - id: queryId, - kqlQuery: '', - name: queryId, - queryMatch: { - field, - value: queryId, - operator: ':' as const, - }, - })); + if (isIcon) { + return ( + + ); + } - return getAddToTimelineButton({ - dataProvider: providers, - field: queryIds[0], - ownFocus: false, - ...(isIcon - ? { showTooltip: true, Component: TimelineIconComponent } - : { Component: TimelineComponent }), - startServices, - }); + return ( + + {addToTimelineLabel} + + ); }; diff --git a/x-pack/platform/plugins/shared/osquery/public/types.ts b/x-pack/platform/plugins/shared/osquery/public/types.ts index e2f88c2a8f3dc..5a70e7f27ec22 100644 --- a/x-pack/platform/plugins/shared/osquery/public/types.ts +++ b/x-pack/platform/plugins/shared/osquery/public/types.ts @@ -17,7 +17,6 @@ import type { TriggersAndActionsUIPublicPluginStart, } from '@kbn/triggers-actions-ui-plugin/public'; import type { CasesPublicStart, CasesPublicSetup } from '@kbn/cases-plugin/public'; -import type { TimelinesUIStart } from '@kbn/timelines-plugin/public'; import type { getLazyLiveQueryField, getLazyOsqueryAction, @@ -27,6 +26,28 @@ import type { import type { useAllLiveQueries, UseAllLiveQueriesConfig } from './actions/use_all_live_queries'; import type { getLazyOsqueryResults } from './shared_components/lazy_osquery_results'; +/** + * Minimal DataProvider type for timeline integration within the osquery plugin. + * This is a local definition to avoid direct dependency on @kbn/timelines-plugin. + * The structure is compatible with the timelines plugin's DataProvider type. + * + * @internal Use this only within the osquery plugin. For security_solution, + * import DataProvider from 'timelines/components/timeline/data_providers/data_provider'. + */ +export interface OsqueryDataProvider { + id: string; + name: string; + enabled: boolean; + excluded: boolean; + kqlQuery: string; + queryMatch: { + field: string; + value: string | string[]; + operator: ':' | ':*' | 'includes'; + }; + and: Omit[]; +} + // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface OsqueryPluginSetup {} @@ -53,7 +74,6 @@ export interface StartPlugins { security: SecurityPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; cases: CasesPublicStart; - timelines: TimelinesUIStart; appName?: string; } @@ -63,3 +83,5 @@ export interface SetupPlugins { } export type StartServices = CoreStart & StartPlugins; + +export type AddToTimelineHandler = (dataProviders: OsqueryDataProvider[]) => void; diff --git a/x-pack/platform/plugins/shared/osquery/tsconfig.json b/x-pack/platform/plugins/shared/osquery/tsconfig.json index d6482a783b2a4..fd995a60d79da 100644 --- a/x-pack/platform/plugins/shared/osquery/tsconfig.json +++ b/x-pack/platform/plugins/shared/osquery/tsconfig.json @@ -43,7 +43,6 @@ "@kbn/lens-plugin", "@kbn/security-plugin", "@kbn/triggers-actions-ui-plugin", - "@kbn/timelines-plugin", "@kbn/spaces-plugin", "@kbn/i18n", "@kbn/rison", diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/response_actions/response_actions_results.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/components/response_actions/response_actions_results.tsx index 9202af136b59f..2f37e87e6c40d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/response_actions/response_actions_results.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/response_actions/response_actions_results.tsx @@ -15,6 +15,7 @@ import type { LogsEndpointActionWithHosts, } from '../../../../common/endpoint/types'; import { useKibana } from '../../lib/kibana'; +import { useAddToTimeline } from '../../hooks/use_add_to_timeline'; interface ResponseActionsResultsProps { actions: Array; @@ -28,6 +29,7 @@ export const ResponseActionsResults = React.memo( services: { osquery }, } = useKibana(); const { OsqueryResult } = osquery; + const addToTimeline = useAddToTimeline(); const getAction = useCallback( (action: LogsEndpointActionWithHosts | LogsOsqueryAction) => { @@ -42,6 +44,7 @@ export const ResponseActionsResults = React.memo( startDate={startDate} ruleName={ruleName} ecsData={ecsData} + addToTimeline={addToTimeline} /> ); } @@ -56,7 +59,7 @@ export const ResponseActionsResults = React.memo( } return null; }, - [OsqueryResult, ecsData, ruleName] + [OsqueryResult, addToTimeline, ecsData, ruleName] ); return ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_add_to_timeline.ts b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_add_to_timeline.ts new file mode 100644 index 0000000000000..ab4e2ffdf42ba --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_add_to_timeline.ts @@ -0,0 +1,52 @@ +/* + * 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 { useCallback } from 'react'; +import { useDispatch } from 'react-redux'; +import { i18n } from '@kbn/i18n'; +import { useKibana } from '../lib/kibana'; +import type { DataProvider } from '../../timelines/components/timeline/data_providers/data_provider'; +import { addProvider } from '../../timelines/store/actions'; +import { TimelineId } from '../../../common/types/timeline'; + +/** + * Hook that returns a callback to add data providers to the active timeline. + * Shows a success toast notification after adding. + */ +export const useAddToTimeline = (): ((dataProviders: DataProvider[]) => void) => { + const dispatch = useDispatch(); + const { notifications } = useKibana().services; + + return useCallback( + (dataProviders: DataProvider[]) => { + if (!dataProviders.length) { + return; + } + + dispatch( + addProvider({ + id: TimelineId.active, + providers: dataProviders, + }) + ); + + const title = + dataProviders.length === 1 + ? i18n.translate('xpack.securitySolution.timeline.addedToTimelineTitle', { + values: { fieldOrValue: dataProviders[0].name }, + defaultMessage: 'Added {fieldOrValue} to Timeline', + }) + : i18n.translate('xpack.securitySolution.timeline.addedMultipleToTimelineTitle', { + values: { count: dataProviders.length }, + defaultMessage: 'Added {count} items to Timeline', + }); + + notifications.toasts.addSuccess({ title }); + }, + [dispatch, notifications.toasts] + ); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/osquery/osquery_flyout.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/osquery/osquery_flyout.tsx index 8e3c7c078fdcb..3b06b235aa54d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/osquery/osquery_flyout.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/osquery/osquery_flyout.tsx @@ -18,6 +18,7 @@ import { import { useQueryClient } from '@kbn/react-query'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { useKibana } from '../../../common/lib/kibana'; +import { useAddToTimeline } from '../../../common/hooks/use_add_to_timeline'; import { OsqueryEventDetailsFooter } from './osquery_flyout_footer'; import { ACTION_OSQUERY } from './translations'; @@ -50,6 +51,7 @@ const OsqueryFlyoutComponent: React.FC = ({ services: { osquery }, } = useKibana(); const queryClient = useQueryClient(); + const addToTimeline = useAddToTimeline(); const invalidateQueries = useCallback(() => { queryClient.invalidateQueries({ @@ -77,6 +79,7 @@ const OsqueryFlyoutComponent: React.FC = ({ defaultValues={defaultValues} ecsData={ecsData} onSuccess={invalidateQueries} + addToTimeline={addToTimeline} />