mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[Security Solution] Add Host/User flyout in One Discover. (#199279)
## Summary Handles https://github.com/elastic/kibana/issues/191998 Follow up work: - https://github.com/elastic/security-team/issues/11112 - https://github.com/elastic/kibana/issues/196667 This PR add below entity flyouts for below entities in One Discover: - host.name - user.name - source.ip - destination.ip In this PR we re-use the security solution code by making use of below model based on `discover-shared` plugin. ```mermaid flowchart TD discoverShared["Discover Shared"] securitySolution["Security Solution"] discover["Discover"] securitySolution -- "registers Features" --> discoverShared discover -- "consume Features" --> discoverShared ``` ## How to Test >[!Note] >This PR adds `security-root-profile` in One discover which is currently in `experimental mode`. All changes below can only be tested when profile is activated. Profile can activated by adding below lines in `config/kibana.dev.yml` > ```yaml > discover.experimental.enabledProfiles: > - security-root-profile > ``` > 1. As mentioned above, adding above experimental flag in `kibana.dev.yml`. 2. Spin up Security Serverless project and add some alert Data. 3. Navigate to Discover and add columns `host.name` and `user.name` in table. Now `host` and `user` flyouts should be available on clicking `host.name`, `user.name`, `source.ip` & `destination.ip`. 4. Flyout should work without any error. 5. Below things are not working and will be tackled in followup PR : - Security Hover actions - Actions such as `Add to Timeline` or `Add to Case` ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
89063df988
commit
c80f91efeb
41 changed files with 728 additions and 81 deletions
|
@ -7,7 +7,7 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { ReactElement } from 'react';
|
||||
import type { FunctionComponent } from 'react';
|
||||
import type { EuiDataGridCellValueElementProps, EuiDataGridColumn } from '@elastic/eui';
|
||||
import type { DataTableRecord } from '@kbn/discover-utils/src/types';
|
||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
|
@ -46,10 +46,7 @@ export type DataGridCellValueElementProps = EuiDataGridCellValueElementProps & {
|
|||
isCompressed?: boolean;
|
||||
};
|
||||
|
||||
export type CustomCellRenderer = Record<
|
||||
string,
|
||||
(props: DataGridCellValueElementProps) => ReactElement
|
||||
>;
|
||||
export type CustomCellRenderer = Record<string, FunctionComponent<DataGridCellValueElementProps>>;
|
||||
|
||||
export interface CustomGridColumnProps {
|
||||
column: EuiDataGridColumn;
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
"unifiedSearch",
|
||||
"unifiedHistogram",
|
||||
"contentManagement",
|
||||
"discoverShared"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"dataVisualizer",
|
||||
|
@ -59,4 +60,4 @@
|
|||
"common"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ import { urlTrackerMock } from './url_tracker.mock';
|
|||
import { createElement } from 'react';
|
||||
import { createContextAwarenessMocks } from '../context_awareness/__mocks__';
|
||||
import { DiscoverEBTManager } from '../services/discover_ebt_manager';
|
||||
import { discoverSharedPluginMock } from '@kbn/discover-shared-plugin/public/mocks';
|
||||
|
||||
export function createDiscoverServicesMock(): DiscoverServices {
|
||||
const dataPlugin = dataPluginMock.createStartContract();
|
||||
|
@ -250,6 +251,7 @@ export function createDiscoverServicesMock(): DiscoverServices {
|
|||
profilesManager: profilesManagerMock,
|
||||
ebtManager: new DiscoverEBTManager(),
|
||||
setHeaderActionMenu: jest.fn(),
|
||||
discoverShared: discoverSharedPluginMock.createStartContract().features,
|
||||
} as unknown as DiscoverServices;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import { ProfileProviderServices } from '../profile_providers/profile_provider_s
|
|||
import { ProfilesManager } from '../profiles_manager';
|
||||
import { DiscoverEBTManager } from '../../services/discover_ebt_manager';
|
||||
import { createLogsContextServiceMock } from '@kbn/discover-utils/src/__mocks__';
|
||||
import { discoverSharedPluginMock } from '@kbn/discover-shared-plugin/public/mocks';
|
||||
|
||||
export const createContextAwarenessMocks = ({
|
||||
shouldRegisterProviders = true,
|
||||
|
@ -181,5 +182,6 @@ export const createContextAwarenessMocks = ({
|
|||
const createProfileProviderServicesMock = () => {
|
||||
return {
|
||||
logsContextService: createLogsContextServiceMock(),
|
||||
discoverShared: discoverSharedPluginMock.createStartContract(),
|
||||
} as ProfileProviderServices;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { SecuritySolutionAppWrapperFeature } from '@kbn/discover-shared-plugin/public';
|
||||
|
||||
export const createAppWrapperAccessor = async (
|
||||
appWrapperFeature?: SecuritySolutionAppWrapperFeature
|
||||
) => {
|
||||
if (!appWrapperFeature) return undefined;
|
||||
return appWrapperFeature.getWrapper();
|
||||
};
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { SecuritySolutionCellRendererFeature } from '@kbn/discover-shared-plugin/public';
|
||||
import { DataGridCellValueElementProps } from '@kbn/unified-data-table';
|
||||
import { createCellRendererAccessor } from './get_cell_renderer_accessor';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
const cellRendererFeature: SecuritySolutionCellRendererFeature = {
|
||||
id: 'security-solution-cell-renderer',
|
||||
getRenderer: async () => (fieldName: string) => {
|
||||
if (fieldName === 'host.name') {
|
||||
return (props: DataGridCellValueElementProps) => {
|
||||
return <div data-test-subj="cell-render-feature">{props.columnId}</div>;
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const mockCellProps = {
|
||||
columnId: 'host.name',
|
||||
row: {
|
||||
id: '1',
|
||||
raw: {},
|
||||
flattened: {},
|
||||
},
|
||||
} as DataGridCellValueElementProps;
|
||||
|
||||
describe('getCellRendererAccessort', () => {
|
||||
it('should return a cell renderer', async () => {
|
||||
const getCellRenderer = await createCellRendererAccessor(cellRendererFeature);
|
||||
expect(getCellRenderer).toBeDefined();
|
||||
const CellRenderer = getCellRenderer?.('host.name') as React.FC<DataGridCellValueElementProps>;
|
||||
expect(CellRenderer).toBeDefined();
|
||||
const { getByTestId } = render(<CellRenderer {...mockCellProps} />);
|
||||
expect(getByTestId('cell-render-feature')).toBeVisible();
|
||||
expect(getByTestId('cell-render-feature')).toHaveTextContent('host.name');
|
||||
});
|
||||
|
||||
it('should return undefined if cellRendererFeature is not defined', async () => {
|
||||
const getCellRenderer = await createCellRendererAccessor();
|
||||
expect(getCellRenderer).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined if cellRendererGetter returns undefined', async () => {
|
||||
const getCellRenderer = await createCellRendererAccessor(cellRendererFeature);
|
||||
const cellRenderer = getCellRenderer?.('user.name');
|
||||
expect(cellRenderer).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { SecuritySolutionCellRendererFeature } from '@kbn/discover-shared-plugin/public';
|
||||
import { DataGridCellValueElementProps } from '@kbn/unified-data-table';
|
||||
|
||||
export const createCellRendererAccessor = async (
|
||||
cellRendererFeature?: SecuritySolutionCellRendererFeature
|
||||
) => {
|
||||
if (!cellRendererFeature) return undefined;
|
||||
const cellRendererGetter = await cellRendererFeature.getRenderer();
|
||||
function getCellRenderer(fieldName: string) {
|
||||
const CellRenderer = cellRendererGetter(fieldName);
|
||||
if (!CellRenderer) return undefined;
|
||||
return React.memo(function SecuritySolutionCellRenderer(props: DataGridCellValueElementProps) {
|
||||
return <CellRenderer {...props} />;
|
||||
});
|
||||
}
|
||||
|
||||
return getCellRenderer;
|
||||
};
|
|
@ -7,25 +7,71 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React, { FunctionComponent, PropsWithChildren } from 'react';
|
||||
import { DataGridCellValueElementProps } from '@kbn/unified-data-table';
|
||||
import { RootProfileProvider, SolutionType } from '../../../profiles';
|
||||
import { ProfileProviderServices } from '../../profile_provider_services';
|
||||
import { SecurityProfileProviderFactory } from '../types';
|
||||
import { createCellRendererAccessor } from '../accessors/get_cell_renderer_accessor';
|
||||
import { createAppWrapperAccessor } from '../accessors/create_app_wrapper_accessor';
|
||||
|
||||
interface SecurityRootProfileContext {
|
||||
appWrapper?: FunctionComponent<PropsWithChildren<{}>>;
|
||||
getCellRenderer?: (
|
||||
fieldName: string
|
||||
) => FunctionComponent<DataGridCellValueElementProps> | undefined;
|
||||
}
|
||||
|
||||
const EmptyAppWrapper: FunctionComponent<PropsWithChildren<{}>> = ({ children }) => <>{children}</>;
|
||||
|
||||
export const createSecurityRootProfileProvider: SecurityProfileProviderFactory<
|
||||
RootProfileProvider
|
||||
> = (services: ProfileProviderServices) => ({
|
||||
profileId: 'security-root-profile',
|
||||
isExperimental: true,
|
||||
profile: {
|
||||
getCellRenderers: (prev) => (params) => ({
|
||||
...prev(params),
|
||||
}),
|
||||
},
|
||||
resolve: (params) => {
|
||||
if (params.solutionNavId === SolutionType.Security) {
|
||||
return { isMatch: true, context: { solutionType: SolutionType.Security } };
|
||||
}
|
||||
RootProfileProvider<SecurityRootProfileContext>
|
||||
> = (services: ProfileProviderServices) => {
|
||||
const { discoverShared } = services;
|
||||
const discoverFeaturesRegistry = discoverShared.features.registry;
|
||||
const cellRendererFeature = discoverFeaturesRegistry.getById('security-solution-cell-renderer');
|
||||
const appWrapperFeature = discoverFeaturesRegistry.getById('security-solution-app-wrapper');
|
||||
|
||||
return { isMatch: false };
|
||||
},
|
||||
});
|
||||
return {
|
||||
profileId: 'security-root-profile',
|
||||
isExperimental: true,
|
||||
profile: {
|
||||
getRenderAppWrapper: (PrevWrapper, params) => {
|
||||
const AppWrapper = params.context.appWrapper ?? EmptyAppWrapper;
|
||||
return ({ children }) => (
|
||||
<PrevWrapper>
|
||||
<AppWrapper>{children}</AppWrapper>
|
||||
</PrevWrapper>
|
||||
);
|
||||
},
|
||||
getCellRenderers:
|
||||
(prev, { context }) =>
|
||||
(params) => {
|
||||
const entries = prev(params);
|
||||
['host.name', 'user.name', 'source.ip', 'destination.ip'].forEach((fieldName) => {
|
||||
entries[fieldName] = context.getCellRenderer?.(fieldName) ?? entries[fieldName];
|
||||
});
|
||||
return entries;
|
||||
},
|
||||
},
|
||||
resolve: async (params) => {
|
||||
if (params.solutionNavId !== SolutionType.Security) {
|
||||
return {
|
||||
isMatch: false,
|
||||
};
|
||||
}
|
||||
|
||||
const getAppWrapper = await createAppWrapperAccessor(appWrapperFeature);
|
||||
const getCellRenderer = await createCellRendererAccessor(cellRendererFeature);
|
||||
|
||||
return {
|
||||
isMatch: true,
|
||||
context: {
|
||||
solutionType: SolutionType.Security,
|
||||
appWrapper: getAppWrapper?.(),
|
||||
getCellRenderer,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ import type { EuiIconType } from '@elastic/eui/src/components/icon/icon';
|
|||
import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import type { OmitIndexSignature } from 'type-fest';
|
||||
import type { Trigger } from '@kbn/ui-actions-plugin/public';
|
||||
import type { PropsWithChildren, ReactElement } from 'react';
|
||||
import type { FunctionComponent, PropsWithChildren } from 'react';
|
||||
import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
|
||||
import type { DiscoverDataSource } from '../../common/data_sources';
|
||||
import type { DiscoverAppState } from '../application/main/state_management/discover_app_state_container';
|
||||
|
@ -268,7 +268,7 @@ export interface Profile {
|
|||
* @param props The app wrapper props
|
||||
* @returns The custom app wrapper component
|
||||
*/
|
||||
getRenderAppWrapper: (props: PropsWithChildren<{}>) => ReactElement;
|
||||
getRenderAppWrapper: FunctionComponent<PropsWithChildren<{}>>;
|
||||
|
||||
/**
|
||||
* Gets default Discover app state that should be used when the profile is resolved
|
||||
|
|
|
@ -38,3 +38,4 @@ export {
|
|||
} from './embeddable';
|
||||
export { loadSharingDataHelpers } from './utils';
|
||||
export { LogsExplorerTabs, type LogsExplorerTabsProps } from './components/logs_explorer_tabs';
|
||||
export type { DiscoverServices } from './build_services';
|
||||
|
|
|
@ -42,7 +42,7 @@ import type { AiopsPluginStart } from '@kbn/aiops-plugin/public';
|
|||
import type { DataVisualizerPluginStart } from '@kbn/data-visualizer-plugin/public';
|
||||
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public';
|
||||
import { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public';
|
||||
import type { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public';
|
||||
import { DiscoverAppLocator } from '../common';
|
||||
import { DiscoverCustomizationContext } from './customizations';
|
||||
import { type DiscoverContainerProps } from './components/discover_container';
|
||||
|
|
|
@ -95,9 +95,9 @@
|
|||
"@kbn/presentation-containers",
|
||||
"@kbn/observability-ai-assistant-plugin",
|
||||
"@kbn/fields-metadata-plugin",
|
||||
"@kbn/discover-contextual-components",
|
||||
"@kbn/logs-data-access-plugin",
|
||||
"@kbn/core-lifecycle-browser",
|
||||
"@kbn/discover-contextual-components",
|
||||
"@kbn/esql-ast",
|
||||
"@kbn/discover-shared-plugin"
|
||||
],
|
||||
|
|
|
@ -17,5 +17,9 @@ export type { DiscoverSharedPublicSetup, DiscoverSharedPublicStart } from './typ
|
|||
export type {
|
||||
ObservabilityLogsAIAssistantFeatureRenderDeps,
|
||||
ObservabilityLogsAIAssistantFeature,
|
||||
SecuritySolutionCellRendererFeature,
|
||||
SecuritySolutionAppWrapperFeature,
|
||||
DiscoverFeature,
|
||||
} from './services/discover_features';
|
||||
DiscoverFeaturesServiceSetup,
|
||||
DiscoverFeaturesServiceStart,
|
||||
} from './services/discover_features/types';
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { DataTableRecord } from '@kbn/discover-utils';
|
||||
import type { DataTableRecord } from '@kbn/discover-utils';
|
||||
import type { FunctionComponent, PropsWithChildren } from 'react';
|
||||
import type { DataGridCellValueElementProps } from '@kbn/unified-data-table';
|
||||
import { FeaturesRegistry } from '../../../common';
|
||||
|
||||
/**
|
||||
|
@ -38,8 +40,31 @@ export interface ObservabilityCreateSLOFeature {
|
|||
}) => React.ReactNode;
|
||||
}
|
||||
|
||||
/** **************** Security Solution ****************/
|
||||
|
||||
export interface SecuritySolutionCellRendererFeature {
|
||||
id: 'security-solution-cell-renderer';
|
||||
getRenderer: () => Promise<
|
||||
(fieldName: string) => FunctionComponent<DataGridCellValueElementProps> | undefined
|
||||
>;
|
||||
}
|
||||
|
||||
export interface SecuritySolutionAppWrapperFeature {
|
||||
id: 'security-solution-app-wrapper';
|
||||
getWrapper: () => Promise<() => FunctionComponent<PropsWithChildren<{}>>>;
|
||||
}
|
||||
|
||||
export type SecuritySolutionFeature =
|
||||
| SecuritySolutionCellRendererFeature
|
||||
| SecuritySolutionAppWrapperFeature;
|
||||
|
||||
/** ****************************************************************************************/
|
||||
|
||||
// This should be a union of all the available client features.
|
||||
export type DiscoverFeature = ObservabilityLogsAIAssistantFeature | ObservabilityCreateSLOFeature;
|
||||
export type DiscoverFeature =
|
||||
| ObservabilityLogsAIAssistantFeature
|
||||
| ObservabilityCreateSLOFeature
|
||||
| SecuritySolutionFeature;
|
||||
|
||||
/**
|
||||
* Service types
|
||||
|
|
|
@ -13,5 +13,6 @@
|
|||
"kbn_references": [
|
||||
"@kbn/discover-utils",
|
||||
"@kbn/core",
|
||||
"@kbn/unified-data-table",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -23,9 +23,10 @@ import { ENROLLMENT_API_KEYS_INDEX } from '../../constants';
|
|||
import { packagePolicyService } from '../package_policy';
|
||||
import { FleetError, HostedAgentPolicyRestrictionRelatedError } from '../../errors';
|
||||
|
||||
import { isSpaceAwarenessEnabled } from './helpers';
|
||||
import type { UninstallTokenSOAttributes } from '../security/uninstall_token_service';
|
||||
|
||||
import { isSpaceAwarenessEnabled } from './helpers';
|
||||
|
||||
export async function updateAgentPolicySpaces({
|
||||
agentPolicyId,
|
||||
currentSpaceId,
|
||||
|
|
|
@ -13,6 +13,10 @@ import type { BrowserFields, TimelineNonEcsData } from '../../../search_strategy
|
|||
|
||||
/** The following props are provided to the function called by `renderCellValue` */
|
||||
export type CellValueElementProps = EuiDataGridCellValueElementProps & {
|
||||
/**
|
||||
* makes sure that field is not rendered as a plain text
|
||||
* but according to the renderer.
|
||||
*/
|
||||
asPlainText?: boolean;
|
||||
browserFields?: BrowserFields;
|
||||
data: TimelineNonEcsData[];
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
"dashboard",
|
||||
"data",
|
||||
"dataViews",
|
||||
"discover",
|
||||
"ecsDataQualityDashboard",
|
||||
"elasticAssistant",
|
||||
"embeddable",
|
||||
|
@ -59,7 +58,8 @@
|
|||
"unifiedDocViewer",
|
||||
"charts",
|
||||
"entityManager",
|
||||
"inference"
|
||||
"inference",
|
||||
"discoverShared"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"encryptedSavedObjects",
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
*/
|
||||
|
||||
import type { CellAction, CellActionFactory } from '@kbn/cell-actions';
|
||||
import { isInSecurityApp } from '../../../../common/hooks/is_in_security_app';
|
||||
import type { SecurityAppStore } from '../../../../common/store';
|
||||
import { isInSecurityApp } from '../../utils';
|
||||
import type { StartServices } from '../../../../types';
|
||||
import { createAddToTimelineCellActionFactory } from '../cell_action/add_to_timeline';
|
||||
|
||||
|
|
|
@ -10,12 +10,13 @@ import { isErrorEmbeddable } from '@kbn/embeddable-plugin/public';
|
|||
import { createAction } from '@kbn/ui-actions-plugin/public';
|
||||
import { apiPublishesUnifiedSearch } from '@kbn/presentation-publishing';
|
||||
import { isLensApi } from '@kbn/lens-plugin/public';
|
||||
import { isInSecurityApp } from '../../../../common/hooks/is_in_security_app';
|
||||
import { KibanaServices } from '../../../../common/lib/kibana';
|
||||
import type { SecurityAppStore } from '../../../../common/store/types';
|
||||
import { addProvider } from '../../../../timelines/store/actions';
|
||||
import type { DataProvider } from '../../../../../common/types';
|
||||
import { EXISTS_OPERATOR, TimelineId } from '../../../../../common/types';
|
||||
import { fieldHasCellActions, isInSecurityApp } from '../../utils';
|
||||
import { fieldHasCellActions } from '../../utils';
|
||||
import {
|
||||
ADD_TO_TIMELINE,
|
||||
ADD_TO_TIMELINE_FAILED_TEXT,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { CellAction, CellActionFactory } from '@kbn/cell-actions';
|
||||
import { isInSecurityApp } from '../../utils';
|
||||
import { isInSecurityApp } from '../../../../common/hooks/is_in_security_app';
|
||||
import type { StartServices } from '../../../../types';
|
||||
import { createCopyToClipboardCellActionFactory } from '../cell_action/copy_to_clipboard';
|
||||
|
||||
|
|
|
@ -9,8 +9,9 @@ import type { CellValueContext, IEmbeddable } from '@kbn/embeddable-plugin/publi
|
|||
import { isErrorEmbeddable } from '@kbn/embeddable-plugin/public';
|
||||
import { createAction } from '@kbn/ui-actions-plugin/public';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { isInSecurityApp } from '../../../../common/hooks/is_in_security_app';
|
||||
import { KibanaServices } from '../../../../common/lib/kibana';
|
||||
import { fieldHasCellActions, isCountField, isInSecurityApp, isLensEmbeddable } from '../../utils';
|
||||
import { fieldHasCellActions, isCountField, isLensEmbeddable } from '../../utils';
|
||||
import { COPY_TO_CLIPBOARD, COPY_TO_CLIPBOARD_ICON, COPY_TO_CLIPBOARD_SUCCESS } from '../constants';
|
||||
|
||||
export const ACTION_ID = 'embeddable_copyToClipboard';
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
*/
|
||||
|
||||
import type { CellAction, CellActionFactory } from '@kbn/cell-actions';
|
||||
import { isInSecurityApp } from '../../../../common/hooks/is_in_security_app';
|
||||
import type { SecurityAppStore } from '../../../../common/store';
|
||||
import { isInSecurityApp } from '../../utils';
|
||||
import type { StartServices } from '../../../../types';
|
||||
import { createFilterInCellActionFactory } from '../cell_action/filter_in';
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { CellActionFactory, CellAction } from '@kbn/cell-actions';
|
||||
import { isInSecurityApp } from '../../utils';
|
||||
import { isInSecurityApp } from '../../../../common/hooks/is_in_security_app';
|
||||
import type { SecurityAppStore } from '../../../../common/store';
|
||||
import type { StartServices } from '../../../../types';
|
||||
import { createFilterOutCellActionFactory } from '../cell_action/filter_out';
|
||||
|
|
|
@ -16,8 +16,9 @@ import type { CellValueContext, IEmbeddable } from '@kbn/embeddable-plugin/publi
|
|||
import { createAction } from '@kbn/ui-actions-plugin/public';
|
||||
import { ACTION_INCOMPATIBLE_VALUE_WARNING } from '@kbn/cell-actions/src/actions/translations';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { isInSecurityApp } from '../../../../common/hooks/is_in_security_app';
|
||||
import { timelineSelectors } from '../../../../timelines/store';
|
||||
import { fieldHasCellActions, isInSecurityApp, isLensEmbeddable } from '../../utils';
|
||||
import { fieldHasCellActions, isLensEmbeddable } from '../../utils';
|
||||
import { TimelineId } from '../../../../../common/types';
|
||||
import { DefaultCellActionTypes } from '../../constants';
|
||||
import type { SecurityAppStore } from '../../../../common/store';
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import type { IEmbeddable } from '@kbn/embeddable-plugin/public';
|
||||
import { isLensApi } from '@kbn/lens-plugin/public';
|
||||
import type { Serializable } from '@kbn/utility-types';
|
||||
import { APP_UI_ID } from '../../../common/constants';
|
||||
|
||||
// All cell actions are disabled for these fields in Security
|
||||
const FIELDS_WITHOUT_CELL_ACTIONS = [
|
||||
|
@ -17,10 +16,6 @@ const FIELDS_WITHOUT_CELL_ACTIONS = [
|
|||
'kibana.alert.reason',
|
||||
];
|
||||
|
||||
export const isInSecurityApp = (currentAppId?: string): boolean => {
|
||||
return !!currentAppId && currentAppId === APP_UI_ID;
|
||||
};
|
||||
|
||||
// @TODO: this is a temporary fix. It needs a better refactor on the consumer side here to
|
||||
// adapt to the new Embeddable architecture
|
||||
export const isLensEmbeddable = (embeddable: IEmbeddable): embeddable is IEmbeddable => {
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 useObservable from 'react-use/lib/useObservable';
|
||||
import { useMemo } from 'react';
|
||||
import { APP_UI_ID } from '../../../common';
|
||||
import { useKibana } from '../lib/kibana';
|
||||
|
||||
export const isInSecurityApp = (currentAppId?: string): boolean => {
|
||||
return !!currentAppId && currentAppId === APP_UI_ID;
|
||||
};
|
||||
|
||||
export const useIsInSecurityApp = () => {
|
||||
const {
|
||||
services: { application },
|
||||
} = useKibana();
|
||||
|
||||
const currentAppId = useObservable(application.currentAppId$);
|
||||
|
||||
return useMemo(() => isInSecurityApp(currentAppId), [currentAppId]);
|
||||
};
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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, { useMemo } from 'react';
|
||||
import { ExpandableFlyoutProvider } from '@kbn/expandable-flyout';
|
||||
import { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
|
||||
import { KibanaContextProvider, useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { NavigationProvider } from '@kbn/security-solution-navigation';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type { SecuritySolutionAppWrapperFeature } from '@kbn/discover-shared-plugin/public';
|
||||
import type { DiscoverServices } from '@kbn/discover-plugin/public';
|
||||
import { CellActionsProvider } from '@kbn/cell-actions';
|
||||
import { APP_ID } from '../../../common';
|
||||
import { SecuritySolutionFlyout } from '../../flyout';
|
||||
import { StatefulEventContext } from '../../common/components/events_viewer/stateful_event_context';
|
||||
import type { SecurityAppStore } from '../../common/store';
|
||||
import { ReactQueryClientProvider } from '../../common/containers/query_client/query_client_provider';
|
||||
import type { StartPluginsDependencies, StartServices } from '../../types';
|
||||
import { MlCapabilitiesProvider } from '../../common/components/ml/permissions/ml_capabilities_provider';
|
||||
import { UserPrivilegesProvider } from '../../common/components/user_privileges/user_privileges_context';
|
||||
import { DiscoverInTimelineContextProvider } from '../../common/components/discover_in_timeline/provider';
|
||||
import { UpsellingProvider } from '../../common/components/upselling_provider';
|
||||
import { ConsoleManager } from '../../management/components/console';
|
||||
import { AssistantProvider } from '../../assistant/provider';
|
||||
import { ONE_DISCOVER_SCOPE_ID } from '../constants';
|
||||
|
||||
export const createSecuritySolutionDiscoverAppWrapperGetter = ({
|
||||
core,
|
||||
services,
|
||||
plugins,
|
||||
store,
|
||||
}: {
|
||||
core: CoreStart;
|
||||
services: StartServices;
|
||||
plugins: StartPluginsDependencies;
|
||||
/**
|
||||
* instance of Security App store that should be used in Discover
|
||||
*/
|
||||
store: SecurityAppStore;
|
||||
}) => {
|
||||
const getSecuritySolutionDiscoverAppWrapper: Awaited<
|
||||
ReturnType<SecuritySolutionAppWrapperFeature['getWrapper']>
|
||||
> = () => {
|
||||
return function SecuritySolutionDiscoverAppWrapper({ children }) {
|
||||
const { services: discoverServices } = useKibana<DiscoverServices>();
|
||||
const CasesContext = useMemo(() => plugins.cases.ui.getCasesContext(), []);
|
||||
|
||||
const userCasesPermissions = useMemo(() => plugins.cases.helpers.canUseCases([APP_ID]), []);
|
||||
|
||||
/**
|
||||
*
|
||||
* Since this component is meant to be used only in the context of Discover,
|
||||
* these services are appended/overwritten to the existing services object
|
||||
* provided by the Discover plugin.
|
||||
*
|
||||
*/
|
||||
const securitySolutionServices: StartServices = useMemo(
|
||||
() => ({
|
||||
...services,
|
||||
/* Helps with getting correct instance of query, timeFilter and filterManager instances from discover */
|
||||
data: discoverServices.data,
|
||||
}),
|
||||
[discoverServices]
|
||||
);
|
||||
|
||||
const statefulEventContextValue = useMemo(
|
||||
() => ({
|
||||
// timelineId acts as scopeId
|
||||
timelineID: ONE_DISCOVER_SCOPE_ID,
|
||||
enableHostDetailsFlyout: true,
|
||||
/* behaviour similar to query tab */
|
||||
tabType: 'query',
|
||||
enableIpDetailsFlyout: true,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<KibanaContextProvider services={securitySolutionServices}>
|
||||
<EuiThemeProvider>
|
||||
<MlCapabilitiesProvider>
|
||||
<CasesContext owner={[APP_ID]} permissions={userCasesPermissions}>
|
||||
<UserPrivilegesProvider kibanaCapabilities={services.application.capabilities}>
|
||||
{/* ^_^ Needed for notes addition */}
|
||||
<NavigationProvider core={core}>
|
||||
<CellActionsProvider
|
||||
getTriggerCompatibleActions={services.uiActions.getTriggerCompatibleActions}
|
||||
>
|
||||
{/* ^_^ Needed for Cell Actions since it gives errors when CellActionsContext is used */}
|
||||
<UpsellingProvider upsellingService={services.upselling}>
|
||||
{/* ^_^ Needed for Alert Preview from Expanded Section of Entity Flyout */}
|
||||
<ReduxStoreProvider store={store}>
|
||||
<ReactQueryClientProvider>
|
||||
<ConsoleManager>
|
||||
{/* ^_^ Needed for AlertPreview -> Alert Details Flyout Action */}
|
||||
<AssistantProvider>
|
||||
{/* ^_^ Needed for AlertPreview -> Alert Details Flyout Action */}
|
||||
<DiscoverInTimelineContextProvider>
|
||||
{/* ^_^ Needed for Add to Timeline action by `useRiskInputActions`*/}
|
||||
<ExpandableFlyoutProvider>
|
||||
<SecuritySolutionFlyout />
|
||||
{/* vv below context should not be here and should be removed */}
|
||||
<StatefulEventContext.Provider
|
||||
value={statefulEventContextValue}
|
||||
>
|
||||
{children}
|
||||
</StatefulEventContext.Provider>
|
||||
</ExpandableFlyoutProvider>
|
||||
</DiscoverInTimelineContextProvider>
|
||||
</AssistantProvider>
|
||||
</ConsoleManager>
|
||||
</ReactQueryClientProvider>
|
||||
</ReduxStoreProvider>
|
||||
</UpsellingProvider>
|
||||
</CellActionsProvider>
|
||||
</NavigationProvider>
|
||||
</UserPrivilegesProvider>
|
||||
</CasesContext>
|
||||
</MlCapabilitiesProvider>
|
||||
</EuiThemeProvider>
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
return getSecuritySolutionDiscoverAppWrapper;
|
||||
};
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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 { DefaultCellRenderer } from '../../timelines/components/timeline/cell_rendering/default_cell_renderer';
|
||||
import { render } from '@testing-library/react';
|
||||
import { getCellRendererForGivenRecord } from './cell_renderers';
|
||||
import type { DataGridCellValueElementProps } from '@kbn/unified-data-table';
|
||||
import type { DataViewField } from '@kbn/data-views-plugin/common';
|
||||
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
|
||||
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
|
||||
|
||||
jest.mock('../../timelines/components/timeline/cell_rendering/default_cell_renderer');
|
||||
|
||||
const DefaultCellRendererMock = DefaultCellRenderer as unknown as jest.Mock<React.ReactElement>;
|
||||
|
||||
/**
|
||||
* Mocking DefaultCellRenderer here because it will be renderered
|
||||
* in Discover's environment and context and we cannot test that here in jest.
|
||||
*
|
||||
* Actual working of Cell Renderer will be tested in Discover's functional tests
|
||||
*
|
||||
* */
|
||||
const mockDefaultCellRenderer = jest.fn((props) => {
|
||||
return <div data-test-subj="mocked-default-cell-render" />;
|
||||
});
|
||||
|
||||
const mockDataView = dataViewMock;
|
||||
mockDataView.getFieldByName = jest.fn().mockReturnValue({ type: 'string' } as DataViewField);
|
||||
|
||||
describe('getCellRendererForGivenRecord', () => {
|
||||
beforeEach(() => {
|
||||
DefaultCellRendererMock.mockImplementation(mockDefaultCellRenderer);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return cell renderer correctly for allowed fields with correct data format', () => {
|
||||
const cellRenderer = getCellRendererForGivenRecord('host.name');
|
||||
expect(cellRenderer).toBeDefined();
|
||||
const props: DataGridCellValueElementProps = {
|
||||
columnId: 'host.name',
|
||||
isDetails: false,
|
||||
isExpanded: false,
|
||||
row: {
|
||||
id: '1',
|
||||
raw: {},
|
||||
flattened: {
|
||||
'host.name': 'host1',
|
||||
'user.name': 'user1',
|
||||
},
|
||||
},
|
||||
dataView: mockDataView,
|
||||
setCellProps: jest.fn(),
|
||||
isExpandable: false,
|
||||
rowIndex: 0,
|
||||
colIndex: 0,
|
||||
fieldFormats: fieldFormatsMock,
|
||||
closePopover: jest.fn(),
|
||||
};
|
||||
const CellRenderer = cellRenderer as React.FC<DataGridCellValueElementProps>;
|
||||
const { getByTestId } = render(<CellRenderer {...props} />);
|
||||
expect(getByTestId('mocked-default-cell-render')).toBeVisible();
|
||||
expect(mockDefaultCellRenderer).toHaveBeenCalledWith(
|
||||
{
|
||||
isDraggable: false,
|
||||
isTimeline: false,
|
||||
isDetails: false,
|
||||
data: [
|
||||
{ field: 'host.name', value: ['host1'] },
|
||||
{ field: 'user.name', value: ['user1'] },
|
||||
],
|
||||
eventId: '1',
|
||||
scopeId: 'one-discover',
|
||||
linkValues: undefined,
|
||||
header: {
|
||||
id: 'host.name',
|
||||
columnHeaderType: 'not-filtered',
|
||||
type: 'string',
|
||||
},
|
||||
asPlainText: false,
|
||||
context: undefined,
|
||||
rowRenderers: undefined,
|
||||
ecsData: undefined,
|
||||
colIndex: 0,
|
||||
rowIndex: 0,
|
||||
isExpandable: false,
|
||||
isExpanded: false,
|
||||
setCellProps: props.setCellProps,
|
||||
columnId: 'host.name',
|
||||
},
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should return undefined for non-allowedFields', () => {
|
||||
const cellRenderer = getCellRendererForGivenRecord('non-allowed-field');
|
||||
expect(cellRenderer).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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, { useMemo } from 'react';
|
||||
import type { DataGridCellValueElementProps } from '@kbn/unified-data-table';
|
||||
import type { TimelineNonEcsData } from '@kbn/timelines-plugin/common';
|
||||
import type { SecuritySolutionCellRendererFeature } from '@kbn/discover-shared-plugin/public';
|
||||
import type { ColumnHeaderType } from '../../../common/types';
|
||||
import type { Maybe } from '../../../common/search_strategy';
|
||||
import { DefaultCellRenderer } from '../../timelines/components/timeline/cell_rendering/default_cell_renderer';
|
||||
import { ONE_DISCOVER_SCOPE_ID } from '../constants';
|
||||
|
||||
export type SecuritySolutionRowCellRendererGetter = Awaited<
|
||||
ReturnType<SecuritySolutionCellRendererFeature['getRenderer']>
|
||||
>;
|
||||
|
||||
const ALLOWED_DISCOVER_RENDERED_FIELDS = ['host.name', 'user.name', 'source.ip', 'destination.ip'];
|
||||
|
||||
export const getCellRendererForGivenRecord: SecuritySolutionRowCellRendererGetter = (
|
||||
fieldName: string
|
||||
) => {
|
||||
if (!ALLOWED_DISCOVER_RENDERED_FIELDS.includes(fieldName)) return undefined;
|
||||
return function UnifiedFieldRenderBySecuritySolution(props: DataGridCellValueElementProps) {
|
||||
// convert discover data format to timeline data format
|
||||
const data: TimelineNonEcsData[] = useMemo(
|
||||
() =>
|
||||
Object.keys(props.row.flattened).map((field) => ({
|
||||
field,
|
||||
value: Array.isArray(props.row.flattened[field])
|
||||
? (props.row.flattened[field] as Maybe<string[]>)
|
||||
: ([props.row.flattened[field]] as Maybe<string[]>),
|
||||
})),
|
||||
[props.row.flattened]
|
||||
);
|
||||
|
||||
const header = useMemo(() => {
|
||||
return {
|
||||
id: props.columnId,
|
||||
columnHeaderType: 'not-filtered' as ColumnHeaderType,
|
||||
type: props.dataView.getFieldByName(props.columnId)?.type,
|
||||
};
|
||||
}, [props.columnId, props.dataView]);
|
||||
|
||||
return (
|
||||
<DefaultCellRenderer
|
||||
data={data}
|
||||
ecsData={undefined}
|
||||
eventId={props.row.id}
|
||||
header={header}
|
||||
isDetails={props.isDetails}
|
||||
isDraggable={false}
|
||||
isTimeline={false}
|
||||
linkValues={undefined}
|
||||
rowRenderers={undefined}
|
||||
scopeId={ONE_DISCOVER_SCOPE_ID}
|
||||
asPlainText={false}
|
||||
context={undefined}
|
||||
isExpandable={props.isExpandable}
|
||||
rowIndex={props.rowIndex}
|
||||
colIndex={props.colIndex}
|
||||
setCellProps={props.setCellProps}
|
||||
isExpanded={props.isExpanded}
|
||||
columnId={props.columnId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { getCellRendererForGivenRecord } from './cell_renderers';
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const ONE_DISCOVER_SCOPE_ID = 'one-discover';
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { getCellRendererForGivenRecord } from './cell_renderers';
|
||||
export { createSecuritySolutionDiscoverAppWrapperGetter } from './app_wrapper';
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../../..',
|
||||
roots: ['<rootDir>/x-pack/plugins/security_solution/public/one_discover'],
|
||||
coverageDirectory:
|
||||
'<rootDir>/target/kibana-coverage/jest/x-pack/plugins/security_solution/public/one_discover',
|
||||
coverageReporters: ['text', 'html'],
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/x-pack/plugins/security_solution/public/one_discover/**/*.{ts,tsx}',
|
||||
],
|
||||
moduleNameMapper: require('../../server/__mocks__/module_name_map'),
|
||||
};
|
|
@ -21,6 +21,10 @@ import { AppStatus, DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
|
|||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { TriggersAndActionsUIPublicPluginSetup } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { uiMetricService } from '@kbn/cloud-security-posture-common/utils/ui_metrics';
|
||||
import type {
|
||||
SecuritySolutionAppWrapperFeature,
|
||||
SecuritySolutionCellRendererFeature,
|
||||
} from '@kbn/discover-shared-plugin/public/services/discover_features';
|
||||
import { getLazyCloudSecurityPosturePliAuthBlockExtension } from './cloud_security_posture/lazy_cloud_security_posture_pli_auth_block_extension';
|
||||
import { getLazyEndpointAgentTamperProtectionExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_agent_tamper_protection_extension';
|
||||
import type {
|
||||
|
@ -70,6 +74,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
// Lazily instantiated dependencies
|
||||
private _subPlugins?: SubPlugins;
|
||||
private _store?: SecurityAppStore;
|
||||
private _securityStoreForDiscover?: SecurityAppStore;
|
||||
private _actionsRegistered?: boolean = false;
|
||||
private _alertsTableRegistered?: boolean = false;
|
||||
|
||||
|
@ -203,6 +208,8 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
getExternalReferenceAttachmentEndpointRegular()
|
||||
);
|
||||
|
||||
this.registerDiscoverSharedFeatures(core, plugins);
|
||||
|
||||
return this.contract.getSetupContract();
|
||||
}
|
||||
|
||||
|
@ -217,6 +224,60 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
this.services.stop();
|
||||
}
|
||||
|
||||
public async registerDiscoverSharedFeatures(
|
||||
core: CoreSetup<StartPluginsDependencies, PluginStart>,
|
||||
plugins: SetupPlugins
|
||||
) {
|
||||
const { discoverShared } = plugins;
|
||||
const discoverFeatureRegistry = discoverShared.features.registry;
|
||||
const cellRendererFeature: SecuritySolutionCellRendererFeature = {
|
||||
id: 'security-solution-cell-renderer',
|
||||
getRenderer: async () => {
|
||||
const { getCellRendererForGivenRecord } = await this.getLazyDiscoverSharedDeps();
|
||||
return getCellRendererForGivenRecord;
|
||||
},
|
||||
};
|
||||
|
||||
const appWrapperFeature: SecuritySolutionAppWrapperFeature = {
|
||||
id: 'security-solution-app-wrapper',
|
||||
getWrapper: async () => {
|
||||
const [coreStart, startPlugins] = await core.getStartServices();
|
||||
|
||||
const services = await this.services.generateServices(coreStart, startPlugins);
|
||||
const subPlugins = await this.startSubPlugins(this.storage, coreStart, startPlugins);
|
||||
const securityStoreForDiscover = await this.getStoreForDiscover(
|
||||
coreStart,
|
||||
startPlugins,
|
||||
subPlugins
|
||||
);
|
||||
|
||||
const { createSecuritySolutionDiscoverAppWrapperGetter } =
|
||||
await this.getLazyDiscoverSharedDeps();
|
||||
|
||||
return createSecuritySolutionDiscoverAppWrapperGetter({
|
||||
core: coreStart,
|
||||
services,
|
||||
plugins: startPlugins,
|
||||
store: securityStoreForDiscover,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
discoverFeatureRegistry.register(cellRendererFeature);
|
||||
discoverFeatureRegistry.register(appWrapperFeature);
|
||||
}
|
||||
|
||||
public async getLazyDiscoverSharedDeps() {
|
||||
/**
|
||||
* The specially formatted comment in the `import` expression causes the corresponding webpack chunk to be named. This aids us in debugging chunk size issues.
|
||||
* See https://webpack.js.org/api/module-methods/#magic-comments
|
||||
*/
|
||||
return import(
|
||||
/* webpackChunkName: "one_discover_shared_deps" */
|
||||
'./one_discover'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* SubPlugins are the individual building blocks of the Security Solution plugin.
|
||||
* They are lazily instantiated to improve startup time.
|
||||
|
@ -311,6 +372,31 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
return this._store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily instantiate a `SecurityAppStore` for discover.
|
||||
*/
|
||||
private async getStoreForDiscover(
|
||||
coreStart: CoreStart,
|
||||
startPlugins: StartPlugins,
|
||||
subPlugins: StartedSubPlugins
|
||||
): Promise<SecurityAppStore> {
|
||||
if (!this._securityStoreForDiscover) {
|
||||
const { createStoreFactory } = await this.lazyApplicationDependencies();
|
||||
|
||||
this._securityStoreForDiscover = await createStoreFactory(
|
||||
coreStart,
|
||||
startPlugins,
|
||||
subPlugins,
|
||||
this.storage,
|
||||
this.experimentalFeatures
|
||||
);
|
||||
}
|
||||
if (startPlugins.timelines) {
|
||||
startPlugins.timelines.setTimelineEmbeddedStore(this._securityStoreForDiscover);
|
||||
}
|
||||
return this._securityStoreForDiscover;
|
||||
}
|
||||
|
||||
private async registerActions(
|
||||
store: SecurityAppStore,
|
||||
history: H.History,
|
||||
|
|
|
@ -12,30 +12,14 @@ import { HostName } from './host_name';
|
|||
import { TestProviders } from '../../../../../common/mock';
|
||||
import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline';
|
||||
import { StatefulEventContext } from '../../../../../common/components/events_viewer/stateful_event_context';
|
||||
import { createTelemetryServiceMock } from '../../../../../common/lib/telemetry/telemetry_service.mock';
|
||||
import { TableId } from '@kbn/securitysolution-data-table';
|
||||
import { createExpandableFlyoutApiMock } from '../../../../../common/mock/expandable_flyout';
|
||||
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||
|
||||
const mockedTelemetry = createTelemetryServiceMock();
|
||||
const mockOpenRightPanel = jest.fn();
|
||||
|
||||
jest.mock('@kbn/expandable-flyout');
|
||||
|
||||
jest.mock('../../../../../common/lib/kibana/kibana_react', () => {
|
||||
return {
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
application: {
|
||||
getUrlForApp: jest.fn(),
|
||||
navigateToApp: jest.fn(),
|
||||
},
|
||||
telemetry: mockedTelemetry,
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../../../common/components/draggables', () => ({
|
||||
DefaultDraggable: () => <div data-test-subj="DefaultDraggable" />,
|
||||
}));
|
||||
|
|
|
@ -15,6 +15,7 @@ import { HostDetailsLink } from '../../../../../common/components/links';
|
|||
import { DefaultDraggable } from '../../../../../common/components/draggables';
|
||||
import { getEmptyTagValue } from '../../../../../common/components/empty_value';
|
||||
import { TruncatableText } from '../../../../../common/components/truncatable_text';
|
||||
import { useIsInSecurityApp } from '../../../../../common/hooks/is_in_security_app';
|
||||
|
||||
interface Props {
|
||||
contextId: string;
|
||||
|
@ -45,6 +46,8 @@ const HostNameComponent: React.FC<Props> = ({
|
|||
}) => {
|
||||
const { openRightPanel } = useExpandableFlyoutApi();
|
||||
|
||||
const isInSecurityApp = useIsInSecurityApp();
|
||||
|
||||
const eventContext = useContext(StatefulEventContext);
|
||||
const hostName = `${value}`;
|
||||
const isInTimelineContext =
|
||||
|
@ -58,6 +61,10 @@ const HostNameComponent: React.FC<Props> = ({
|
|||
onClick();
|
||||
}
|
||||
|
||||
/*
|
||||
* if and only if renderer is running inside security solution app
|
||||
* we check for event and timeline context
|
||||
* */
|
||||
if (!eventContext || !isInTimelineContext) {
|
||||
return;
|
||||
}
|
||||
|
@ -85,13 +92,21 @@ const HostNameComponent: React.FC<Props> = ({
|
|||
Component={Component}
|
||||
hostName={hostName}
|
||||
isButton={isButton}
|
||||
onClick={isInTimelineContext ? openHostDetailsSidePanel : undefined}
|
||||
onClick={isInTimelineContext || !isInSecurityApp ? openHostDetailsSidePanel : undefined}
|
||||
title={title}
|
||||
>
|
||||
<TruncatableText data-test-subj="draggable-truncatable-content">{hostName}</TruncatableText>
|
||||
</HostDetailsLink>
|
||||
),
|
||||
[Component, hostName, isButton, isInTimelineContext, openHostDetailsSidePanel, title]
|
||||
[
|
||||
Component,
|
||||
hostName,
|
||||
isButton,
|
||||
isInTimelineContext,
|
||||
openHostDetailsSidePanel,
|
||||
title,
|
||||
isInSecurityApp,
|
||||
]
|
||||
);
|
||||
|
||||
return isString(value) && hostName.length > 0 ? (
|
||||
|
|
|
@ -12,30 +12,14 @@ import { TestProviders } from '../../../../../common/mock';
|
|||
import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline';
|
||||
import { UserName } from './user_name';
|
||||
import { StatefulEventContext } from '../../../../../common/components/events_viewer/stateful_event_context';
|
||||
import { createTelemetryServiceMock } from '../../../../../common/lib/telemetry/telemetry_service.mock';
|
||||
import { TableId } from '@kbn/securitysolution-data-table';
|
||||
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||
import { createExpandableFlyoutApiMock } from '../../../../../common/mock/expandable_flyout';
|
||||
|
||||
const mockedTelemetry = createTelemetryServiceMock();
|
||||
const mockOpenRightPanel = jest.fn();
|
||||
|
||||
jest.mock('@kbn/expandable-flyout');
|
||||
|
||||
jest.mock('../../../../../common/lib/kibana/kibana_react', () => {
|
||||
return {
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
application: {
|
||||
getUrlForApp: jest.fn(),
|
||||
navigateToApp: jest.fn(),
|
||||
},
|
||||
telemetry: mockedTelemetry,
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../../../common/components/draggables', () => ({
|
||||
DefaultDraggable: () => <div data-test-subj="DefaultDraggable" />,
|
||||
}));
|
||||
|
|
|
@ -15,6 +15,7 @@ import { DefaultDraggable } from '../../../../../common/components/draggables';
|
|||
import { getEmptyTagValue } from '../../../../../common/components/empty_value';
|
||||
import { UserDetailsLink } from '../../../../../common/components/links';
|
||||
import { TruncatableText } from '../../../../../common/components/truncatable_text';
|
||||
import { useIsInSecurityApp } from '../../../../../common/hooks/is_in_security_app';
|
||||
|
||||
interface Props {
|
||||
contextId: string;
|
||||
|
@ -48,6 +49,8 @@ const UserNameComponent: React.FC<Props> = ({
|
|||
const isInTimelineContext = userName && eventContext?.timelineID;
|
||||
const { openRightPanel } = useExpandableFlyoutApi();
|
||||
|
||||
const isInSecurityApp = useIsInSecurityApp();
|
||||
|
||||
const openUserDetailsSidePanel = useCallback(
|
||||
(e: React.SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
|
@ -83,13 +86,21 @@ const UserNameComponent: React.FC<Props> = ({
|
|||
Component={Component}
|
||||
userName={userName}
|
||||
isButton={isButton}
|
||||
onClick={isInTimelineContext ? openUserDetailsSidePanel : undefined}
|
||||
onClick={isInTimelineContext || !isInSecurityApp ? openUserDetailsSidePanel : undefined}
|
||||
title={title}
|
||||
>
|
||||
<TruncatableText data-test-subj="draggable-truncatable-content">{userName}</TruncatableText>
|
||||
</UserDetailsLink>
|
||||
),
|
||||
[userName, isButton, isInTimelineContext, openUserDetailsSidePanel, Component, title]
|
||||
[
|
||||
userName,
|
||||
isButton,
|
||||
isInTimelineContext,
|
||||
openUserDetailsSidePanel,
|
||||
Component,
|
||||
title,
|
||||
isInSecurityApp,
|
||||
]
|
||||
);
|
||||
|
||||
return isString(value) && userName.length > 0 ? (
|
||||
|
|
|
@ -61,6 +61,7 @@ import type { PluginStartContract } from '@kbn/alerting-plugin/public/plugin';
|
|||
import type { MapsStartApi } from '@kbn/maps-plugin/public';
|
||||
import type { IntegrationAssistantPluginStart } from '@kbn/integration-assistant-plugin/public';
|
||||
import type { ServerlessPluginStart } from '@kbn/serverless/public';
|
||||
import type { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public';
|
||||
import type { ResolverPluginSetup } from './resolver/types';
|
||||
import type { Inspect } from '../common/search_strategy';
|
||||
import type { Detections } from './detections';
|
||||
|
@ -107,6 +108,7 @@ export interface SetupPlugins {
|
|||
ml?: MlPluginSetup;
|
||||
cases?: CasesPublicSetup;
|
||||
data: DataPublicPluginSetup;
|
||||
discoverShared: DiscoverSharedPublicStart;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,7 +15,11 @@
|
|||
"public/**/*.json",
|
||||
"../../../typings/**/*"
|
||||
],
|
||||
"exclude": ["target/**/*", "**/cypress/**", "public/management/cypress.config.ts"],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
"**/cypress/**",
|
||||
"public/management/cypress.config.ts"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
{
|
||||
|
@ -228,6 +232,7 @@
|
|||
"@kbn/core-lifecycle-server",
|
||||
"@kbn/core-user-profile-common",
|
||||
"@kbn/langchain",
|
||||
"@kbn/discover-shared-plugin",
|
||||
"@kbn/react-hooks",
|
||||
"@kbn/index-adapter",
|
||||
"@kbn/core-http-server-utils"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue