[Osquery] Refactor addToCases and addToTimeline usage (#142365)

This commit is contained in:
Tomasz Ciecierski 2022-10-18 15:32:52 +02:00 committed by GitHub
parent 74595dee9b
commit b24732a966
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 230 additions and 400 deletions

View file

@ -5,21 +5,29 @@
* 2.0.
*/
import React from 'react';
import React, { useContext } from 'react';
import { CasesAttachmentWrapperContext } from '../shared_components/attachments/pack_queries_attachment_wrapper';
import { useKibana } from '../common/lib/kibana';
import type { AddToCaseButtonProps } from './add_to_cases_button';
import { AddToCaseButton } from './add_to_cases_button';
const CASES_OWNER: string[] = [];
export const AddToCaseWrapper: React.FC<AddToCaseButtonProps> = React.memo((props) => {
export const AddToCaseWrapper = React.memo<AddToCaseButtonProps>((props) => {
const { cases } = useKibana().services;
const isCasesAttachment = useContext(CasesAttachmentWrapperContext);
if (isCasesAttachment || !props.actionId) {
return <></>;
}
const casePermissions = cases.helpers.canUseCases();
const CasesContext = cases.ui.getCasesContext();
return (
<CasesContext owner={CASES_OWNER} permissions={casePermissions}>
<AddToCaseButton {...props} />{' '}
<AddToCaseButton {...props} />
</CasesContext>
);
});

View file

@ -20,7 +20,7 @@ const ADD_TO_CASE = i18n.translate(
);
export interface AddToCaseButtonProps {
queryId: string;
queryId?: string;
agentIds?: string[];
actionId: string;
isIcon?: boolean;
@ -67,6 +67,7 @@ export const AddToCaseButton: React.FC<AddToCaseButtonProps> = ({
iconType={'casesApp'}
onClick={handleClick}
isDisabled={isDisabled || !hasCasesPermissions}
aria-label={ADD_TO_CASE}
{...iconProps}
/>
</EuiToolTip>
@ -79,6 +80,7 @@ export const AddToCaseButton: React.FC<AddToCaseButtonProps> = ({
iconType="casesApp"
onClick={handleClick}
isDisabled={isDisabled || !hasCasesPermissions}
aria-label={ADD_TO_CASE}
>
{ADD_TO_CASE}
</EuiButtonEmpty>

View file

@ -12,8 +12,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useForm as useHookForm, FormProvider } from 'react-hook-form';
import { isEmpty, find, pickBy } from 'lodash';
import { AddToCaseWrapper } from '../../cases/add_to_cases';
import type { AddToTimelinePayload } from '../../timelines/get_add_to_timeline';
import { PLUGIN_NAME as OSQUERY_PLUGIN_NAME } from '../../../common';
import { QueryPackSelectable } from './query_pack_selectable';
import type { SavedQuerySOFormData } from '../../saved_queries/form/use_saved_query_form';
import { useKibana } from '../../common/lib/kibana';
@ -56,7 +55,6 @@ interface LiveQueryFormProps {
formType?: FormType;
enabled?: boolean;
hideAgentsField?: boolean;
addToTimeline?: (payload: AddToTimelinePayload) => React.ReactElement;
}
const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
@ -66,9 +64,9 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
formType = 'steps',
enabled = true,
hideAgentsField = false,
addToTimeline,
}) => {
const permissions = useKibana().services.application.capabilities.osquery;
const { application, appName } = useKibana().services;
const permissions = application.capabilities.osquery;
const canRunPacks = useMemo(
() =>
!!((permissions.runSavedQueries || permissions.writeLiveQueries) && permissions.readPacks),
@ -211,26 +209,6 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
const singleQueryDetails = useMemo(() => liveQueryDetails?.queries?.[0], [liveQueryDetails]);
const liveQueryActionId = useMemo(() => liveQueryDetails?.action_id, [liveQueryDetails]);
const agentIds = useMemo(() => liveQueryDetails?.agents, [liveQueryDetails?.agents]);
const addToCaseButton = useCallback(
(payload) => {
if (liveQueryActionId) {
return (
<AddToCaseWrapper
queryId={payload.queryId}
agentIds={agentIds}
actionId={liveQueryActionId}
isIcon={payload.isIcon}
isDisabled={payload.isDisabled}
/>
);
}
return <></>;
},
[agentIds, liveQueryActionId]
);
const resultsStepContent = useMemo(
() =>
@ -240,8 +218,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
ecsMapping={serializedData.ecs_mapping}
endDate={singleQueryDetails?.expiration}
agentIds={singleQueryDetails?.agents}
addToTimeline={addToTimeline}
addToCase={addToCaseButton}
liveQueryActionId={liveQueryActionId}
/>
) : null,
[
@ -249,8 +226,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
singleQueryDetails?.expiration,
singleQueryDetails?.agents,
serializedData.ecs_mapping,
addToTimeline,
addToCaseButton,
liveQueryActionId,
]
);
@ -330,9 +306,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
{queryType === 'pack' ? (
<PackFieldWrapper
liveQueryDetails={liveQueryDetails}
addToTimeline={addToTimeline}
submitButtonContent={submitButtonContent}
addToCase={addToCaseButton}
showResultsHeader
/>
) : (
@ -349,7 +323,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
{showSavedQueryFlyout ? (
<SavedQueryFlyout
isExternal={!!addToTimeline}
isExternal={appName !== OSQUERY_PLUGIN_NAME}
onClose={handleCloseSaveQueryFlyout}
defaultValue={serializedData}
/>

View file

@ -6,7 +6,6 @@
*/
import { get, map } from 'lodash';
import type { ReactElement } from 'react';
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import {
EuiBasicTable,
@ -24,7 +23,6 @@ import {
import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import type { ECSMapping } from '@kbn/osquery-io-ts-types';
import type { AddToTimelinePayload } from '../../timelines/get_add_to_timeline';
import { PackResultsHeader } from './pack_results_header';
import { Direction } from '../../../common/search_strategy';
import { removeMultilines } from '../../../common/utils/build_query/remove_multilines';
@ -32,6 +30,8 @@ import { ResultTabs } from '../../routes/saved_queries/edit/tabs';
import type { PackItem } from '../../packs/types';
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';
const TruncateTooltipText = styled.div`
width: 100%;
@ -116,22 +116,10 @@ type PackQueryStatusItem = Partial<{
interface PackQueriesStatusTableProps {
agentIds?: string[];
queryId?: string;
actionId?: string;
actionId: string | undefined;
data?: PackQueryStatusItem[];
startDate?: string;
expirationDate?: string;
addToTimeline?: (payload: AddToTimelinePayload) => ReactElement;
addToCase?: ({
actionId,
isIcon,
isDisabled,
queryId,
}: {
actionId?: string;
isIcon?: boolean;
isDisabled?: boolean;
queryId?: string;
}) => ReactElement;
showResultsHeader?: boolean;
}
@ -142,8 +130,6 @@ const PackQueriesStatusTableComponent: React.FC<PackQueriesStatusTableProps> = (
data,
startDate,
expirationDate,
addToTimeline,
addToCase,
showResultsHeader,
}) => {
const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<Record<string, unknown>>({});
@ -199,22 +185,7 @@ const PackQueriesStatusTableComponent: React.FC<PackQueriesStatusTableProps> = (
);
const renderLensResultsAction = useCallback((item) => <PackViewInLensAction item={item} />, []);
const handleAddToCase = useCallback(
(payload: { actionId?: string; isIcon?: boolean; queryId: string }) =>
// eslint-disable-next-line react/display-name
() => {
if (addToCase) {
return addToCase({
actionId: payload.actionId,
isIcon: payload.isIcon,
queryId: payload.queryId,
});
}
return <></>;
},
[addToCase]
);
const getHandleErrorsToggle = useCallback(
(item) => () => {
setItemIdToExpandedRowMap((prevValue) => {
@ -226,14 +197,13 @@ const PackQueriesStatusTableComponent: React.FC<PackQueriesStatusTableProps> = (
<EuiFlexGroup gutterSize="xl">
<EuiFlexItem>
<ResultTabs
liveQueryActionId={actionId}
actionId={item.action_id}
startDate={startDate}
ecsMapping={item.ecs_mapping}
endDate={expirationDate}
agentIds={agentIds}
failedAgentsCount={item?.failed ?? 0}
addToTimeline={addToTimeline}
addToCase={addToCase && handleAddToCase({ queryId: item.action_id, actionId })}
/>
</EuiFlexItem>
</EuiFlexGroup>
@ -243,7 +213,7 @@ const PackQueriesStatusTableComponent: React.FC<PackQueriesStatusTableProps> = (
return itemIdToExpandedRowMapValues;
});
},
[startDate, expirationDate, agentIds, addToTimeline, addToCase, handleAddToCase, actionId]
[actionId, startDate, expirationDate, agentIds]
);
const renderToggleResultsAction = useCallback(
@ -272,24 +242,27 @@ const PackQueriesStatusTableComponent: React.FC<PackQueriesStatusTableProps> = (
render: renderLensResultsAction,
},
{
render: (item: { action_id: string }) =>
addToTimeline && addToTimeline({ query: ['action_id', item.action_id], isIcon: true }),
render: (item: { action_id: string }) => (
<AddToTimelineButton field="action_id" value={item.action_id} isIcon={true} />
),
},
{
render: (item: { action_id: string }) =>
addToCase &&
addToCase({
actionId,
queryId: item.action_id,
isIcon: true,
isDisabled: !item.action_id,
}),
actionId && (
<AddToCaseWrapper
actionId={actionId}
agentIds={agentIds}
queryId={item.action_id}
isIcon={true}
isDisabled={!item.action_id}
/>
),
},
];
return resultActions.map((action) => action.render(row));
},
[actionId, addToCase, addToTimeline, renderDiscoverResultsAction, renderLensResultsAction]
[actionId, agentIds, renderDiscoverResultsAction, renderLensResultsAction]
);
const columns = useMemo(
() => [
@ -377,19 +350,16 @@ const PackQueriesStatusTableComponent: React.FC<PackQueriesStatusTableProps> = (
}
}, [agentIds?.length, data, getHandleErrorsToggle, itemIdToExpandedRowMap]);
const queryIds = useMemo(
() =>
map(data, (query) => ({
value: query.action_id || '',
field: 'action_id',
})),
[data]
);
const queryIds = useMemo(() => map(data, (query) => query.action_id), [data]);
return (
<>
{showResultsHeader && (
<PackResultsHeader queryIds={queryIds} actionId={actionId} addToCase={addToCase} />
<PackResultsHeader
queryIds={queryIds as string[]}
actionId={actionId}
agentIds={agentIds}
/>
)}
<StyledEuiBasicTable

View file

@ -4,22 +4,17 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { ReactElement } from 'react';
import React from 'react';
import React, { useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import styled from 'styled-components';
import { AddToTimelineButton } from '../../timelines/add_to_timeline_button';
import { AddToCaseWrapper } from '../../cases/add_to_cases';
interface PackResultsHeadersProps {
actionId?: string;
addToCase?: ({
isIcon,
iconProps,
}: {
isIcon: boolean;
iconProps: Record<string, string>;
}) => ReactElement;
queryIds: Array<{ value: string; field: string }>;
queryIds: string[];
agentIds?: string[];
}
const StyledResultsHeading = styled(EuiFlexItem)`
@ -33,35 +28,53 @@ const StyledIconsList = styled(EuiFlexItem)`
padding-left: 10px;
`;
export const PackResultsHeader = ({ actionId, addToCase }: PackResultsHeadersProps) => (
<>
<EuiSpacer size={'l'} />
<EuiFlexGroup direction="row" gutterSize="m">
<StyledResultsHeading grow={false}>
<EuiText>
<h2>
<FormattedMessage
id="xpack.osquery.liveQueryActionResults.results"
defaultMessage="Results"
/>
</h2>
</EuiText>
</StyledResultsHeading>
<StyledIconsList grow={false}>
<span>
{actionId &&
addToCase &&
addToCase({
isIcon: true,
iconProps: {
color: 'text',
size: 'xs',
iconSize: 'l',
},
})}
</span>
</StyledIconsList>
</EuiFlexGroup>
<EuiSpacer size={'l'} />
</>
export const PackResultsHeader = React.memo<PackResultsHeadersProps>(
({ actionId, agentIds, queryIds }) => {
const iconProps = useMemo(() => ({ color: 'text', size: 'xs', iconSize: 'l' }), []);
return (
<>
<EuiSpacer size={'l'} />
<EuiFlexGroup direction="row" gutterSize="m">
<StyledResultsHeading grow={false}>
<EuiText>
<h2>
<FormattedMessage
id="xpack.osquery.liveQueryActionResults.results"
defaultMessage="Results"
/>
</h2>
</EuiText>
</StyledResultsHeading>
<StyledIconsList grow={false}>
<span>
{actionId && (
<EuiFlexGroup>
<EuiFlexItem>
<AddToCaseWrapper
actionId={actionId}
agentIds={agentIds}
isIcon={true}
iconProps={iconProps}
/>
</EuiFlexItem>
<EuiFlexItem>
<AddToTimelineButton
field="action_id"
value={queryIds}
isIcon={true}
iconProps={iconProps}
/>
</EuiFlexItem>
</EuiFlexGroup>
)}
</span>
</StyledIconsList>
</EuiFlexGroup>
<EuiSpacer size={'l'} />
</>
);
}
);
PackResultsHeader.displayName = 'PackResultsHeader';

View file

@ -11,7 +11,6 @@ import React, { useMemo } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import type { ECSMapping } from '@kbn/osquery-io-ts-types';
import type { AddToTimelinePayload } from '../timelines/get_add_to_timeline';
import { LiveQueryForm } from './form';
import { useActionResultsPrivileges } from '../action_results/use_action_privileges';
import { OSQUERY_INTEGRATION_NAME } from '../../common';
@ -35,7 +34,6 @@ interface LiveQueryProps {
hideAgentsField?: boolean;
packId?: string;
agentSelection?: AgentSelection;
addToTimeline?: (payload: AddToTimelinePayload) => React.ReactElement;
}
const LiveQueryComponent: React.FC<LiveQueryProps> = ({
@ -55,7 +53,6 @@ const LiveQueryComponent: React.FC<LiveQueryProps> = ({
hideAgentsField,
packId,
agentSelection,
addToTimeline,
}) => {
const { data: hasActionResultsPrivileges, isLoading } = useActionResultsPrivileges();
@ -132,7 +129,6 @@ const LiveQueryComponent: React.FC<LiveQueryProps> = ({
formType={formType}
enabled={enabled}
hideAgentsField={hideAgentsField}
addToTimeline={addToTimeline}
/>
);
};

View file

@ -28,7 +28,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react';
import type { ECSMapping } from '@kbn/osquery-io-ts-types';
import { pagePathGetters } from '@kbn/fleet-plugin/public';
import type { AddToTimelinePayload } from '../timelines/get_add_to_timeline';
import { AddToTimelineButton } from '../timelines/add_to_timeline_button';
import { useAllResults } from './use_all_results';
import type { ResultEdges } from '../../common/search_strategy';
import { Direction } from '../../common/search_strategy';
@ -41,7 +41,8 @@ import {
ViewResultsActionButtonType,
} from '../packs/pack_queries_status_table';
import { useActionResultsPrivileges } from '../action_results/use_action_privileges';
import { OSQUERY_INTEGRATION_NAME } from '../../common';
import { OSQUERY_INTEGRATION_NAME, PLUGIN_NAME as OSQUERY_PLUGIN_NAME } from '../../common';
import { AddToCaseWrapper } from '../cases/add_to_cases';
const DataContext = createContext<ResultEdges>([]);
@ -52,8 +53,7 @@ export interface ResultsTableComponentProps {
ecsMapping?: ECSMapping;
endDate?: string;
startDate?: string;
addToTimeline?: (payload: AddToTimelinePayload) => React.ReactElement;
addToCase?: ({ actionId }: { actionId?: string }) => React.ReactElement;
liveQueryActionId?: string;
}
const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
@ -62,8 +62,7 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
ecsMapping,
startDate,
endDate,
addToTimeline,
addToCase,
liveQueryActionId,
}) => {
const [isLive, setIsLive] = useState(true);
const { data: hasActionResultsPrivileges } = useActionResultsPrivileges();
@ -82,7 +81,11 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
skip: !hasActionResultsPrivileges,
});
const expired = useMemo(() => (!endDate ? false : new Date(endDate) < new Date()), [endDate]);
const { getUrlForApp } = useKibana().services.application;
const {
application: { getUrlForApp },
appName,
timelines,
} = useKibana().services;
const getFleetAppUrl = useCallback(
(agentId) =>
@ -305,7 +308,7 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
const leadingControlColumns: EuiDataGridControlColumn[] = useMemo(() => {
const data = allResultsData?.edges;
if (addToTimeline && data) {
if (timelines && data) {
return [
{
id: 'timeline',
@ -317,19 +320,19 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
};
const eventId = data[visibleRowIndex]?._id;
return addToTimeline({ query: ['_id', eventId], isIcon: true });
return <AddToTimelineButton field="_id" value={eventId} isIcon={true} />;
},
},
];
}
return [];
}, [addToTimeline, allResultsData?.edges]);
}, [allResultsData?.edges, timelines]);
const toolbarVisibility = useMemo(
() => ({
showDisplaySelector: false,
showFullScreenSelector: !addToTimeline,
showFullScreenSelector: appName === OSQUERY_PLUGIN_NAME,
additionalControls: (
<>
<ViewResultsInDiscoverAction
@ -344,12 +347,14 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
endDate={endDate}
startDate={startDate}
/>
{addToTimeline && addToTimeline({ query: ['action_id', actionId] })}
{addToCase && addToCase({ actionId })}
<AddToTimelineButton field="action_id" value={actionId} />
{liveQueryActionId && (
<AddToCaseWrapper actionId={liveQueryActionId} queryId={actionId} agentIds={agentIds} />
)}
</>
),
}),
[actionId, addToCase, addToTimeline, endDate, startDate]
[actionId, agentIds, appName, endDate, liveQueryActionId, startDate]
);
useEffect(

View file

@ -7,11 +7,10 @@
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import React, { useLayoutEffect, useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import styled from 'styled-components';
import { AddToCaseWrapper } from '../../../cases/add_to_cases';
import { useRouterNavigate } from '../../../common/lib/kibana';
import { WithHeaderLayout } from '../../../components/layouts';
import { useLiveQueryDetails } from '../../../actions/use_live_query_details';
@ -58,19 +57,6 @@ const LiveQueryDetailsPageComponent = () => {
useLayoutEffect(() => {
setIsLive(() => !(data?.status === 'completed'));
}, [data?.status]);
const addToCaseButton = useCallback(
(payload) => (
<AddToCaseWrapper
queryId={payload.queryId}
actionId={actionId}
agentIds={data?.agents}
isIcon={payload.isIcon}
isDisabled={payload.isDisabled}
iconProps={payload.iconProps}
/>
),
[data?.agents, actionId]
);
return (
<WithHeaderLayout leftColumn={LeftColumn} rightColumnGrow={false}>
@ -81,7 +67,6 @@ const LiveQueryDetailsPageComponent = () => {
startDate={data?.['@timestamp']}
expirationDate={data?.expiration}
agentIds={data?.agents}
addToCase={addToCaseButton}
showResultsHeader
/>
</StyledTableWrapper>

View file

@ -7,10 +7,8 @@
import { EuiTabbedContent, EuiNotificationBadge } from '@elastic/eui';
import React, { useMemo } from 'react';
import type { ReactElement } from 'react';
import type { ECSMapping } from '@kbn/osquery-io-ts-types';
import type { AddToTimelinePayload } from '../../../timelines/get_add_to_timeline';
import { ResultsTable } from '../../../results/results_table';
import { ActionResultsSummary } from '../../../action_results/action_results_summary';
@ -21,8 +19,7 @@ interface ResultTabsProps {
ecsMapping?: ECSMapping;
failedAgentsCount?: number;
endDate?: string;
addToTimeline?: (payload: AddToTimelinePayload) => ReactElement;
addToCase?: ({ actionId }: { actionId?: string }) => ReactElement;
liveQueryActionId?: string;
}
const ResultTabsComponent: React.FC<ResultTabsProps> = ({
@ -32,8 +29,7 @@ const ResultTabsComponent: React.FC<ResultTabsProps> = ({
endDate,
failedAgentsCount,
startDate,
addToTimeline,
addToCase,
liveQueryActionId,
}) => {
const tabs = useMemo(
() => [
@ -47,8 +43,7 @@ const ResultTabsComponent: React.FC<ResultTabsProps> = ({
ecsMapping={ecsMapping}
startDate={startDate}
endDate={endDate}
addToTimeline={addToTimeline}
addToCase={addToCase}
liveQueryActionId={liveQueryActionId}
/>
),
},
@ -65,16 +60,7 @@ const ResultTabsComponent: React.FC<ResultTabsProps> = ({
) : null,
},
],
[
actionId,
agentIds,
ecsMapping,
startDate,
endDate,
addToTimeline,
addToCase,
failedAgentsCount,
]
[actionId, agentIds, ecsMapping, startDate, endDate, liveQueryActionId, failedAgentsCount]
);
return (

View file

@ -5,14 +5,12 @@
* 2.0.
*/
import React, { useCallback, useLayoutEffect, useState } from 'react';
import { useKibana } from '../../common/lib/kibana';
import { getAddToTimeline } from '../../timelines/get_add_to_timeline';
import React, { useLayoutEffect, useState } from 'react';
import { PackQueriesStatusTable } from '../../live_queries/form/pack_queries_status_table';
import { useLiveQueryDetails } from '../../actions/use_live_query_details';
interface PackQueriesAttachmentWrapperProps {
actionId?: string;
actionId: string;
agentIds: string[];
queryId: string;
}
@ -22,11 +20,7 @@ export const PackQueriesAttachmentWrapper = ({
agentIds,
queryId,
}: PackQueriesAttachmentWrapperProps) => {
const {
services: { timelines, appName },
} = useKibana();
const [isLive, setIsLive] = useState(false);
const addToTimelineButton = getAddToTimeline(timelines, appName);
const { data } = useLiveQueryDetails({
actionId,
@ -38,26 +32,18 @@ export const PackQueriesAttachmentWrapper = ({
setIsLive(() => !(data?.status === 'completed'));
}, [data?.status]);
const addToTimeline = useCallback(
(payload) => {
if (!actionId || !addToTimelineButton) {
return <></>;
}
return addToTimelineButton(payload);
},
[actionId, addToTimelineButton]
);
return (
<PackQueriesStatusTable
actionId={actionId}
queryId={queryId}
data={data?.queries}
startDate={data?.['@timestamp']}
expirationDate={data?.expiration}
agentIds={agentIds}
addToTimeline={addToTimeline}
/>
<CasesAttachmentWrapperContext.Provider value={true}>
<PackQueriesStatusTable
actionId={actionId}
queryId={queryId}
data={data?.queries}
startDate={data?.['@timestamp']}
expirationDate={data?.expiration}
agentIds={agentIds}
/>
</CasesAttachmentWrapperContext.Provider>
);
};
export const CasesAttachmentWrapperContext = React.createContext(false);

View file

@ -10,7 +10,6 @@ import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { OsqueryEmptyPrompt, OsqueryNotAvailablePrompt } from '../prompts';
import type { AddToTimelinePayload } from '../../timelines/get_add_to_timeline';
import { AGENT_STATUS_ERROR, PERMISSION_DENIED, SHORT_EMPTY_TITLE } from './translations';
import { useKibana } from '../../common/lib/kibana';
import { LiveQuery } from '../../live_queries';
@ -22,7 +21,6 @@ export interface OsqueryActionProps {
defaultValues?: {};
formType: 'steps' | 'simple';
hideAgentsField?: boolean;
addToTimeline?: (payload: AddToTimelinePayload) => React.ReactElement;
}
const OsqueryActionComponent: React.FC<OsqueryActionProps> = ({
@ -30,7 +28,6 @@ const OsqueryActionComponent: React.FC<OsqueryActionProps> = ({
formType = 'simple',
defaultValues,
hideAgentsField,
addToTimeline,
}) => {
const permissions = useKibana().services.application.capabilities.osquery;
@ -94,7 +91,6 @@ const OsqueryActionComponent: React.FC<OsqueryActionProps> = ({
formType={formType}
agentId={agentId}
hideAgentsField={hideAgentsField}
addToTimeline={addToTimeline}
{...defaultValues}
/>
);

View file

@ -6,7 +6,6 @@
*/
import { EuiFlexItem, EuiSpacer } from '@elastic/eui';
import type { ReactElement } from 'react';
import React, { useMemo } from 'react';
import { find } from 'lodash';
import { useWatch } from 'react-hook-form';
@ -25,17 +24,13 @@ interface PackFieldWrapperProps {
action_id?: string;
agents?: string[];
};
addToTimeline?: (payload: { query: [string, string]; isIcon?: true }) => React.ReactElement;
submitButtonContent?: React.ReactNode;
addToCase?: ({ actionId }: { actionId?: string }) => ReactElement;
showResultsHeader?: boolean;
}
export const PackFieldWrapper = ({
liveQueryDetails,
addToTimeline,
submitButtonContent,
addToCase,
showResultsHeader,
}: PackFieldWrapperProps) => {
const { data: packsData } = usePacks({});
@ -69,8 +64,6 @@ export const PackFieldWrapper = ({
agentIds={agentIds}
// @ts-expect-error update types
data={liveQueryDetails?.queries ?? selectedPackData?.attributes?.queries}
addToTimeline={addToTimeline}
addToCase={addToCase}
showResultsHeader={showResultsHeader}
/>
</EuiFlexItem>

View file

@ -24,7 +24,6 @@ const OsqueryActionResultsComponent: React.FC<OsqueryActionResultsProps> = ({
agentIds,
ruleName,
alertId,
addToTimeline,
}) => {
const { data: actionsData } = useAllLiveQueries({
filterQuery: { term: { alert_ids: alertId } },
@ -49,7 +48,6 @@ const OsqueryActionResultsComponent: React.FC<OsqueryActionResultsProps> = ({
queryId={queryId}
startDate={startDate}
ruleName={ruleName}
addToTimeline={addToTimeline}
agentIds={agentIds}
/>
);

View file

@ -6,10 +6,9 @@
*/
import { EuiComment, EuiSpacer } from '@elastic/eui';
import React, { useCallback } from 'react';
import React from 'react';
import { FormattedRelative } from '@kbn/i18n-react';
import { AddToCaseWrapper } from '../../cases/add_to_cases';
import type { OsqueryActionResultsProps } from './types';
import { useLiveQueryDetails } from '../../actions/use_live_query_details';
import { ATTACHED_QUERY } from '../../agents/translations';
@ -21,52 +20,32 @@ interface OsqueryResultProps extends Omit<OsqueryActionResultsProps, 'alertId'>
startDate: string;
}
export const OsqueryResult = ({
actionId,
ruleName,
addToTimeline,
agentIds,
startDate,
}: OsqueryResultProps) => {
const { data } = useLiveQueryDetails({
actionId,
});
export const OsqueryResult = React.memo<OsqueryResultProps>(
({ actionId, ruleName, agentIds, startDate }) => {
const { data } = useLiveQueryDetails({
actionId,
});
const addToCaseButton = useCallback(
(payload) => (
<AddToCaseWrapper
queryId={payload.queryId}
actionId={actionId}
agentIds={data?.agents}
isIcon={payload.isIcon}
isDisabled={payload.isDisabled}
iconProps={payload.iconProps}
/>
),
[data?.agents, actionId]
);
return (
<div>
<EuiSpacer size="s" />
<EuiComment
username={ruleName && ruleName[0]}
timestamp={<FormattedRelative value={startDate} />}
event={ATTACHED_QUERY}
data-test-subj={'osquery-results-comment'}
>
<PackQueriesStatusTable
actionId={actionId}
// queryId={queryId}
data={data?.queries}
startDate={data?.['@timestamp']}
expirationDate={data?.expiration}
agentIds={agentIds}
addToTimeline={addToTimeline}
addToCase={addToCaseButton}
/>
</EuiComment>
<EuiSpacer size="s" />
</div>
);
};
return (
<div>
<EuiSpacer size="s" />
<EuiComment
username={ruleName && ruleName[0]}
timestamp={<FormattedRelative value={startDate} />}
event={ATTACHED_QUERY}
data-test-subj={'osquery-results-comment'}
>
<PackQueriesStatusTable
actionId={actionId}
// queryId={queryId}
data={data?.queries}
startDate={data?.['@timestamp']}
expirationDate={data?.expiration}
agentIds={agentIds}
/>
</EuiComment>
<EuiSpacer size="s" />
</div>
);
}
);

View file

@ -5,11 +5,8 @@
* 2.0.
*/
import type React from 'react';
export interface OsqueryActionResultsProps {
agentIds?: string[];
ruleName?: string[];
alertId: string;
addToTimeline?: (payload: { query: [string, string]; isIcon?: true }) => React.ReactElement;
}

View file

@ -0,0 +1,64 @@
/*
* 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, { useCallback } from 'react';
import { isArray } from 'lodash';
import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui';
import { useKibana } from '../common/lib/kibana';
const TimelineComponent = React.memo((props) => <EuiButtonEmpty {...props} size="xs" />);
TimelineComponent.displayName = 'TimelineComponent';
export interface AddToTimelineButtonProps {
field: string;
value: string | string[];
isIcon?: true;
iconProps?: Record<string, string>;
}
export const SECURITY_APP_NAME = 'Security';
export const AddToTimelineButton = (props: AddToTimelineButtonProps) => {
const { timelines, appName } = useKibana().services;
const { field, value, isIcon, iconProps } = props;
const queryIds = isArray(value) ? value : [value];
const TimelineIconComponent = useCallback(
(timelineComponentProps) => (
<EuiButtonIcon iconType={'timelines'} {...timelineComponentProps} size="xs" {...iconProps} />
),
[iconProps]
);
if (!timelines || appName !== SECURITY_APP_NAME || !queryIds.length) {
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,
},
}));
return getAddToTimelineButton({
dataProvider: providers,
field: queryIds[0],
ownFocus: false,
...(isIcon
? { showTooltip: true, Component: TimelineIconComponent }
: { Component: TimelineComponent }),
});
};

View file

@ -1,58 +0,0 @@
/*
* 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 from 'react';
import { EuiButtonEmpty } from '@elastic/eui';
import type { ServicesWrapperProps } from '../shared_components/services_wrapper';
const TimelineComponent = React.memo((props) => <EuiButtonEmpty {...props} size="xs" />);
TimelineComponent.displayName = 'TimelineComponent';
export interface AddToTimelinePayload {
query: [string, string];
isIcon?: true;
}
export const SECURITY_APP_NAME = 'Security';
export const getAddToTimeline = (
timelines: ServicesWrapperProps['services']['timelines'],
appName?: string
) => {
if (!timelines || appName !== SECURITY_APP_NAME) {
return;
}
const { getAddToTimelineButton } = timelines.getHoverActions();
return (payload: AddToTimelinePayload) => {
const {
query: [field, value],
isIcon,
} = payload;
const providerA = {
and: [],
enabled: true,
excluded: false,
id: value,
kqlQuery: '',
name: value,
queryMatch: {
field,
value,
operator: ':' as const,
},
};
return getAddToTimelineButton({
dataProvider: providerA,
field: value,
ownFocus: false,
...(isIcon ? { showTooltip: true } : { Component: TimelineComponent }),
});
};
};

View file

@ -1,53 +0,0 @@
/*
* 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, { useCallback } from 'react';
import { EuiButtonEmpty } from '@elastic/eui';
import type { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider';
import { useKibana } from '../../lib/kibana';
const TimelineComponent = React.memo((props) => {
return <EuiButtonEmpty {...props} size="xs" />;
});
TimelineComponent.displayName = 'TimelineComponent';
export const useHandleAddToTimeline = () => {
const {
services: { timelines },
} = useKibana();
const { getAddToTimelineButton } = timelines.getHoverActions();
return useCallback(
(payload: { query: [string, string]; isIcon?: true }) => {
const {
query: [field, value],
isIcon,
} = payload;
const providerA: DataProvider = {
and: [],
enabled: true,
excluded: false,
id: value,
kqlQuery: '',
name: value,
queryMatch: {
field,
value,
operator: ':',
},
};
return getAddToTimelineButton({
dataProvider: providerA,
field: value,
ownFocus: false,
...(isIcon ? { showTooltip: true } : { Component: TimelineComponent }),
});
},
[getAddToTimelineButton]
);
};

View file

@ -22,7 +22,6 @@ import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_fe
import { useKibana } from '../../lib/kibana';
import { EventsViewType } from './event_details';
import * as i18n from './translations';
import { useHandleAddToTimeline } from './add_to_timeline_button';
const TabContentWrapper = styled.div`
height: 100%;
@ -64,7 +63,6 @@ export const useOsqueryTab = ({ rawEventData }: { rawEventData?: AlertRawEventDa
const {
services: { osquery, application },
} = useKibana();
const handleAddToTimeline = useHandleAddToTimeline();
const responseActionsEnabled = useIsExperimentalFeatureEnabled('responseActionsEnabled');
const emptyPrompt = useMemo(
@ -139,12 +137,7 @@ export const useOsqueryTab = ({ rawEventData }: { rawEventData?: AlertRawEventDa
{!application?.capabilities?.osquery?.read ? (
emptyPrompt
) : (
<OsqueryResults
agentIds={agentIds}
ruleName={ruleName}
alertId={alertId}
addToTimeline={handleAddToTimeline}
/>
<OsqueryResults agentIds={agentIds} ruleName={ruleName} alertId={alertId} />
)}
</TabContentWrapper>
</>

View file

@ -8,7 +8,6 @@
import React from 'react';
import styled from 'styled-components';
import { EuiFlyout, EuiFlyoutFooter, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui';
import { useHandleAddToTimeline } from '../../../common/components/event_details/add_to_timeline_button';
import { useKibana } from '../../../common/lib/kibana';
import { OsqueryEventDetailsFooter } from './osquery_flyout_footer';
import { ACTION_OSQUERY } from './translations';
@ -31,8 +30,6 @@ export const OsqueryFlyoutComponent: React.FC<OsqueryFlyoutProps> = ({
services: { osquery },
} = useKibana();
const handleAddToTimeline = useHandleAddToTimeline();
if (osquery?.OsqueryAction) {
return (
<EuiFlyout
@ -52,7 +49,6 @@ export const OsqueryFlyoutComponent: React.FC<OsqueryFlyoutProps> = ({
agentId={agentId}
formType="steps"
defaultValues={defaultValues}
addToTimeline={handleAddToTimeline}
/>
</OsqueryActionWrapper>
</EuiFlyoutBody>