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".
|
* 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 { EuiDataGridCellValueElementProps, EuiDataGridColumn } from '@elastic/eui';
|
||||||
import type { DataTableRecord } from '@kbn/discover-utils/src/types';
|
import type { DataTableRecord } from '@kbn/discover-utils/src/types';
|
||||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||||
|
@ -46,10 +46,7 @@ export type DataGridCellValueElementProps = EuiDataGridCellValueElementProps & {
|
||||||
isCompressed?: boolean;
|
isCompressed?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CustomCellRenderer = Record<
|
export type CustomCellRenderer = Record<string, FunctionComponent<DataGridCellValueElementProps>>;
|
||||||
string,
|
|
||||||
(props: DataGridCellValueElementProps) => ReactElement
|
|
||||||
>;
|
|
||||||
|
|
||||||
export interface CustomGridColumnProps {
|
export interface CustomGridColumnProps {
|
||||||
column: EuiDataGridColumn;
|
column: EuiDataGridColumn;
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
"unifiedSearch",
|
"unifiedSearch",
|
||||||
"unifiedHistogram",
|
"unifiedHistogram",
|
||||||
"contentManagement",
|
"contentManagement",
|
||||||
|
"discoverShared"
|
||||||
],
|
],
|
||||||
"optionalPlugins": [
|
"optionalPlugins": [
|
||||||
"dataVisualizer",
|
"dataVisualizer",
|
||||||
|
|
|
@ -47,6 +47,7 @@ import { urlTrackerMock } from './url_tracker.mock';
|
||||||
import { createElement } from 'react';
|
import { createElement } from 'react';
|
||||||
import { createContextAwarenessMocks } from '../context_awareness/__mocks__';
|
import { createContextAwarenessMocks } from '../context_awareness/__mocks__';
|
||||||
import { DiscoverEBTManager } from '../services/discover_ebt_manager';
|
import { DiscoverEBTManager } from '../services/discover_ebt_manager';
|
||||||
|
import { discoverSharedPluginMock } from '@kbn/discover-shared-plugin/public/mocks';
|
||||||
|
|
||||||
export function createDiscoverServicesMock(): DiscoverServices {
|
export function createDiscoverServicesMock(): DiscoverServices {
|
||||||
const dataPlugin = dataPluginMock.createStartContract();
|
const dataPlugin = dataPluginMock.createStartContract();
|
||||||
|
@ -250,6 +251,7 @@ export function createDiscoverServicesMock(): DiscoverServices {
|
||||||
profilesManager: profilesManagerMock,
|
profilesManager: profilesManagerMock,
|
||||||
ebtManager: new DiscoverEBTManager(),
|
ebtManager: new DiscoverEBTManager(),
|
||||||
setHeaderActionMenu: jest.fn(),
|
setHeaderActionMenu: jest.fn(),
|
||||||
|
discoverShared: discoverSharedPluginMock.createStartContract().features,
|
||||||
} as unknown as DiscoverServices;
|
} as unknown as DiscoverServices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { ProfileProviderServices } from '../profile_providers/profile_provider_s
|
||||||
import { ProfilesManager } from '../profiles_manager';
|
import { ProfilesManager } from '../profiles_manager';
|
||||||
import { DiscoverEBTManager } from '../../services/discover_ebt_manager';
|
import { DiscoverEBTManager } from '../../services/discover_ebt_manager';
|
||||||
import { createLogsContextServiceMock } from '@kbn/discover-utils/src/__mocks__';
|
import { createLogsContextServiceMock } from '@kbn/discover-utils/src/__mocks__';
|
||||||
|
import { discoverSharedPluginMock } from '@kbn/discover-shared-plugin/public/mocks';
|
||||||
|
|
||||||
export const createContextAwarenessMocks = ({
|
export const createContextAwarenessMocks = ({
|
||||||
shouldRegisterProviders = true,
|
shouldRegisterProviders = true,
|
||||||
|
@ -181,5 +182,6 @@ export const createContextAwarenessMocks = ({
|
||||||
const createProfileProviderServicesMock = () => {
|
const createProfileProviderServicesMock = () => {
|
||||||
return {
|
return {
|
||||||
logsContextService: createLogsContextServiceMock(),
|
logsContextService: createLogsContextServiceMock(),
|
||||||
|
discoverShared: discoverSharedPluginMock.createStartContract(),
|
||||||
} as ProfileProviderServices;
|
} 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".
|
* 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 { RootProfileProvider, SolutionType } from '../../../profiles';
|
||||||
import { ProfileProviderServices } from '../../profile_provider_services';
|
import { ProfileProviderServices } from '../../profile_provider_services';
|
||||||
import { SecurityProfileProviderFactory } from '../types';
|
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<
|
export const createSecurityRootProfileProvider: SecurityProfileProviderFactory<
|
||||||
RootProfileProvider
|
RootProfileProvider<SecurityRootProfileContext>
|
||||||
> = (services: ProfileProviderServices) => ({
|
> = (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 {
|
||||||
profileId: 'security-root-profile',
|
profileId: 'security-root-profile',
|
||||||
isExperimental: true,
|
isExperimental: true,
|
||||||
profile: {
|
profile: {
|
||||||
getCellRenderers: (prev) => (params) => ({
|
getRenderAppWrapper: (PrevWrapper, params) => {
|
||||||
...prev(params),
|
const AppWrapper = params.context.appWrapper ?? EmptyAppWrapper;
|
||||||
}),
|
return ({ children }) => (
|
||||||
|
<PrevWrapper>
|
||||||
|
<AppWrapper>{children}</AppWrapper>
|
||||||
|
</PrevWrapper>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
resolve: (params) => {
|
getCellRenderers:
|
||||||
if (params.solutionNavId === SolutionType.Security) {
|
(prev, { context }) =>
|
||||||
return { isMatch: true, context: { solutionType: SolutionType.Security } };
|
(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,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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 { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
|
||||||
import type { OmitIndexSignature } from 'type-fest';
|
import type { OmitIndexSignature } from 'type-fest';
|
||||||
import type { Trigger } from '@kbn/ui-actions-plugin/public';
|
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 { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
|
||||||
import type { DiscoverDataSource } from '../../common/data_sources';
|
import type { DiscoverDataSource } from '../../common/data_sources';
|
||||||
import type { DiscoverAppState } from '../application/main/state_management/discover_app_state_container';
|
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
|
* @param props The app wrapper props
|
||||||
* @returns The custom app wrapper component
|
* @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
|
* Gets default Discover app state that should be used when the profile is resolved
|
||||||
|
|
|
@ -38,3 +38,4 @@ export {
|
||||||
} from './embeddable';
|
} from './embeddable';
|
||||||
export { loadSharingDataHelpers } from './utils';
|
export { loadSharingDataHelpers } from './utils';
|
||||||
export { LogsExplorerTabs, type LogsExplorerTabsProps } from './components/logs_explorer_tabs';
|
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 { DataVisualizerPluginStart } from '@kbn/data-visualizer-plugin/public';
|
||||||
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||||
import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-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 { DiscoverAppLocator } from '../common';
|
||||||
import { DiscoverCustomizationContext } from './customizations';
|
import { DiscoverCustomizationContext } from './customizations';
|
||||||
import { type DiscoverContainerProps } from './components/discover_container';
|
import { type DiscoverContainerProps } from './components/discover_container';
|
||||||
|
|
|
@ -95,9 +95,9 @@
|
||||||
"@kbn/presentation-containers",
|
"@kbn/presentation-containers",
|
||||||
"@kbn/observability-ai-assistant-plugin",
|
"@kbn/observability-ai-assistant-plugin",
|
||||||
"@kbn/fields-metadata-plugin",
|
"@kbn/fields-metadata-plugin",
|
||||||
|
"@kbn/discover-contextual-components",
|
||||||
"@kbn/logs-data-access-plugin",
|
"@kbn/logs-data-access-plugin",
|
||||||
"@kbn/core-lifecycle-browser",
|
"@kbn/core-lifecycle-browser",
|
||||||
"@kbn/discover-contextual-components",
|
|
||||||
"@kbn/esql-ast",
|
"@kbn/esql-ast",
|
||||||
"@kbn/discover-shared-plugin"
|
"@kbn/discover-shared-plugin"
|
||||||
],
|
],
|
||||||
|
|
|
@ -17,5 +17,9 @@ export type { DiscoverSharedPublicSetup, DiscoverSharedPublicStart } from './typ
|
||||||
export type {
|
export type {
|
||||||
ObservabilityLogsAIAssistantFeatureRenderDeps,
|
ObservabilityLogsAIAssistantFeatureRenderDeps,
|
||||||
ObservabilityLogsAIAssistantFeature,
|
ObservabilityLogsAIAssistantFeature,
|
||||||
|
SecuritySolutionCellRendererFeature,
|
||||||
|
SecuritySolutionAppWrapperFeature,
|
||||||
DiscoverFeature,
|
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".
|
* 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';
|
import { FeaturesRegistry } from '../../../common';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,8 +40,31 @@ export interface ObservabilityCreateSLOFeature {
|
||||||
}) => React.ReactNode;
|
}) => 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.
|
// This should be a union of all the available client features.
|
||||||
export type DiscoverFeature = ObservabilityLogsAIAssistantFeature | ObservabilityCreateSLOFeature;
|
export type DiscoverFeature =
|
||||||
|
| ObservabilityLogsAIAssistantFeature
|
||||||
|
| ObservabilityCreateSLOFeature
|
||||||
|
| SecuritySolutionFeature;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service types
|
* Service types
|
||||||
|
|
|
@ -13,5 +13,6 @@
|
||||||
"kbn_references": [
|
"kbn_references": [
|
||||||
"@kbn/discover-utils",
|
"@kbn/discover-utils",
|
||||||
"@kbn/core",
|
"@kbn/core",
|
||||||
|
"@kbn/unified-data-table",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,10 @@ import { ENROLLMENT_API_KEYS_INDEX } from '../../constants';
|
||||||
import { packagePolicyService } from '../package_policy';
|
import { packagePolicyService } from '../package_policy';
|
||||||
import { FleetError, HostedAgentPolicyRestrictionRelatedError } from '../../errors';
|
import { FleetError, HostedAgentPolicyRestrictionRelatedError } from '../../errors';
|
||||||
|
|
||||||
import { isSpaceAwarenessEnabled } from './helpers';
|
|
||||||
import type { UninstallTokenSOAttributes } from '../security/uninstall_token_service';
|
import type { UninstallTokenSOAttributes } from '../security/uninstall_token_service';
|
||||||
|
|
||||||
|
import { isSpaceAwarenessEnabled } from './helpers';
|
||||||
|
|
||||||
export async function updateAgentPolicySpaces({
|
export async function updateAgentPolicySpaces({
|
||||||
agentPolicyId,
|
agentPolicyId,
|
||||||
currentSpaceId,
|
currentSpaceId,
|
||||||
|
|
|
@ -13,6 +13,10 @@ import type { BrowserFields, TimelineNonEcsData } from '../../../search_strategy
|
||||||
|
|
||||||
/** The following props are provided to the function called by `renderCellValue` */
|
/** The following props are provided to the function called by `renderCellValue` */
|
||||||
export type CellValueElementProps = EuiDataGridCellValueElementProps & {
|
export type CellValueElementProps = EuiDataGridCellValueElementProps & {
|
||||||
|
/**
|
||||||
|
* makes sure that field is not rendered as a plain text
|
||||||
|
* but according to the renderer.
|
||||||
|
*/
|
||||||
asPlainText?: boolean;
|
asPlainText?: boolean;
|
||||||
browserFields?: BrowserFields;
|
browserFields?: BrowserFields;
|
||||||
data: TimelineNonEcsData[];
|
data: TimelineNonEcsData[];
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
"dashboard",
|
"dashboard",
|
||||||
"data",
|
"data",
|
||||||
"dataViews",
|
"dataViews",
|
||||||
"discover",
|
|
||||||
"ecsDataQualityDashboard",
|
"ecsDataQualityDashboard",
|
||||||
"elasticAssistant",
|
"elasticAssistant",
|
||||||
"embeddable",
|
"embeddable",
|
||||||
|
@ -59,7 +58,8 @@
|
||||||
"unifiedDocViewer",
|
"unifiedDocViewer",
|
||||||
"charts",
|
"charts",
|
||||||
"entityManager",
|
"entityManager",
|
||||||
"inference"
|
"inference",
|
||||||
|
"discoverShared"
|
||||||
],
|
],
|
||||||
"optionalPlugins": [
|
"optionalPlugins": [
|
||||||
"encryptedSavedObjects",
|
"encryptedSavedObjects",
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CellAction, CellActionFactory } from '@kbn/cell-actions';
|
import type { CellAction, CellActionFactory } from '@kbn/cell-actions';
|
||||||
|
import { isInSecurityApp } from '../../../../common/hooks/is_in_security_app';
|
||||||
import type { SecurityAppStore } from '../../../../common/store';
|
import type { SecurityAppStore } from '../../../../common/store';
|
||||||
import { isInSecurityApp } from '../../utils';
|
|
||||||
import type { StartServices } from '../../../../types';
|
import type { StartServices } from '../../../../types';
|
||||||
import { createAddToTimelineCellActionFactory } from '../cell_action/add_to_timeline';
|
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 { createAction } from '@kbn/ui-actions-plugin/public';
|
||||||
import { apiPublishesUnifiedSearch } from '@kbn/presentation-publishing';
|
import { apiPublishesUnifiedSearch } from '@kbn/presentation-publishing';
|
||||||
import { isLensApi } from '@kbn/lens-plugin/public';
|
import { isLensApi } from '@kbn/lens-plugin/public';
|
||||||
|
import { isInSecurityApp } from '../../../../common/hooks/is_in_security_app';
|
||||||
import { KibanaServices } from '../../../../common/lib/kibana';
|
import { KibanaServices } from '../../../../common/lib/kibana';
|
||||||
import type { SecurityAppStore } from '../../../../common/store/types';
|
import type { SecurityAppStore } from '../../../../common/store/types';
|
||||||
import { addProvider } from '../../../../timelines/store/actions';
|
import { addProvider } from '../../../../timelines/store/actions';
|
||||||
import type { DataProvider } from '../../../../../common/types';
|
import type { DataProvider } from '../../../../../common/types';
|
||||||
import { EXISTS_OPERATOR, TimelineId } from '../../../../../common/types';
|
import { EXISTS_OPERATOR, TimelineId } from '../../../../../common/types';
|
||||||
import { fieldHasCellActions, isInSecurityApp } from '../../utils';
|
import { fieldHasCellActions } from '../../utils';
|
||||||
import {
|
import {
|
||||||
ADD_TO_TIMELINE,
|
ADD_TO_TIMELINE,
|
||||||
ADD_TO_TIMELINE_FAILED_TEXT,
|
ADD_TO_TIMELINE_FAILED_TEXT,
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CellAction, CellActionFactory } from '@kbn/cell-actions';
|
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 type { StartServices } from '../../../../types';
|
||||||
import { createCopyToClipboardCellActionFactory } from '../cell_action/copy_to_clipboard';
|
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 { isErrorEmbeddable } from '@kbn/embeddable-plugin/public';
|
||||||
import { createAction } from '@kbn/ui-actions-plugin/public';
|
import { createAction } from '@kbn/ui-actions-plugin/public';
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
|
import { isInSecurityApp } from '../../../../common/hooks/is_in_security_app';
|
||||||
import { KibanaServices } from '../../../../common/lib/kibana';
|
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';
|
import { COPY_TO_CLIPBOARD, COPY_TO_CLIPBOARD_ICON, COPY_TO_CLIPBOARD_SUCCESS } from '../constants';
|
||||||
|
|
||||||
export const ACTION_ID = 'embeddable_copyToClipboard';
|
export const ACTION_ID = 'embeddable_copyToClipboard';
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CellAction, CellActionFactory } from '@kbn/cell-actions';
|
import type { CellAction, CellActionFactory } from '@kbn/cell-actions';
|
||||||
|
import { isInSecurityApp } from '../../../../common/hooks/is_in_security_app';
|
||||||
import type { SecurityAppStore } from '../../../../common/store';
|
import type { SecurityAppStore } from '../../../../common/store';
|
||||||
import { isInSecurityApp } from '../../utils';
|
|
||||||
import type { StartServices } from '../../../../types';
|
import type { StartServices } from '../../../../types';
|
||||||
import { createFilterInCellActionFactory } from '../cell_action/filter_in';
|
import { createFilterInCellActionFactory } from '../cell_action/filter_in';
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CellActionFactory, CellAction } from '@kbn/cell-actions';
|
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 { SecurityAppStore } from '../../../../common/store';
|
||||||
import type { StartServices } from '../../../../types';
|
import type { StartServices } from '../../../../types';
|
||||||
import { createFilterOutCellActionFactory } from '../cell_action/filter_out';
|
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 { createAction } from '@kbn/ui-actions-plugin/public';
|
||||||
import { ACTION_INCOMPATIBLE_VALUE_WARNING } from '@kbn/cell-actions/src/actions/translations';
|
import { ACTION_INCOMPATIBLE_VALUE_WARNING } from '@kbn/cell-actions/src/actions/translations';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { isInSecurityApp } from '../../../../common/hooks/is_in_security_app';
|
||||||
import { timelineSelectors } from '../../../../timelines/store';
|
import { timelineSelectors } from '../../../../timelines/store';
|
||||||
import { fieldHasCellActions, isInSecurityApp, isLensEmbeddable } from '../../utils';
|
import { fieldHasCellActions, isLensEmbeddable } from '../../utils';
|
||||||
import { TimelineId } from '../../../../../common/types';
|
import { TimelineId } from '../../../../../common/types';
|
||||||
import { DefaultCellActionTypes } from '../../constants';
|
import { DefaultCellActionTypes } from '../../constants';
|
||||||
import type { SecurityAppStore } from '../../../../common/store';
|
import type { SecurityAppStore } from '../../../../common/store';
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
import type { IEmbeddable } from '@kbn/embeddable-plugin/public';
|
import type { IEmbeddable } from '@kbn/embeddable-plugin/public';
|
||||||
import { isLensApi } from '@kbn/lens-plugin/public';
|
import { isLensApi } from '@kbn/lens-plugin/public';
|
||||||
import type { Serializable } from '@kbn/utility-types';
|
import type { Serializable } from '@kbn/utility-types';
|
||||||
import { APP_UI_ID } from '../../../common/constants';
|
|
||||||
|
|
||||||
// All cell actions are disabled for these fields in Security
|
// All cell actions are disabled for these fields in Security
|
||||||
const FIELDS_WITHOUT_CELL_ACTIONS = [
|
const FIELDS_WITHOUT_CELL_ACTIONS = [
|
||||||
|
@ -17,10 +16,6 @@ const FIELDS_WITHOUT_CELL_ACTIONS = [
|
||||||
'kibana.alert.reason',
|
'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
|
// @TODO: this is a temporary fix. It needs a better refactor on the consumer side here to
|
||||||
// adapt to the new Embeddable architecture
|
// adapt to the new Embeddable architecture
|
||||||
export const isLensEmbeddable = (embeddable: IEmbeddable): embeddable is IEmbeddable => {
|
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 { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||||
import type { TriggersAndActionsUIPublicPluginSetup } from '@kbn/triggers-actions-ui-plugin/public';
|
import type { TriggersAndActionsUIPublicPluginSetup } from '@kbn/triggers-actions-ui-plugin/public';
|
||||||
import { uiMetricService } from '@kbn/cloud-security-posture-common/utils/ui_metrics';
|
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 { 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 { getLazyEndpointAgentTamperProtectionExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_agent_tamper_protection_extension';
|
||||||
import type {
|
import type {
|
||||||
|
@ -70,6 +74,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
||||||
// Lazily instantiated dependencies
|
// Lazily instantiated dependencies
|
||||||
private _subPlugins?: SubPlugins;
|
private _subPlugins?: SubPlugins;
|
||||||
private _store?: SecurityAppStore;
|
private _store?: SecurityAppStore;
|
||||||
|
private _securityStoreForDiscover?: SecurityAppStore;
|
||||||
private _actionsRegistered?: boolean = false;
|
private _actionsRegistered?: boolean = false;
|
||||||
private _alertsTableRegistered?: boolean = false;
|
private _alertsTableRegistered?: boolean = false;
|
||||||
|
|
||||||
|
@ -203,6 +208,8 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
||||||
getExternalReferenceAttachmentEndpointRegular()
|
getExternalReferenceAttachmentEndpointRegular()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.registerDiscoverSharedFeatures(core, plugins);
|
||||||
|
|
||||||
return this.contract.getSetupContract();
|
return this.contract.getSetupContract();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,6 +224,60 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
||||||
this.services.stop();
|
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.
|
* SubPlugins are the individual building blocks of the Security Solution plugin.
|
||||||
* They are lazily instantiated to improve startup time.
|
* They are lazily instantiated to improve startup time.
|
||||||
|
@ -311,6 +372,31 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
||||||
return this._store;
|
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(
|
private async registerActions(
|
||||||
store: SecurityAppStore,
|
store: SecurityAppStore,
|
||||||
history: H.History,
|
history: H.History,
|
||||||
|
|
|
@ -12,30 +12,14 @@ import { HostName } from './host_name';
|
||||||
import { TestProviders } from '../../../../../common/mock';
|
import { TestProviders } from '../../../../../common/mock';
|
||||||
import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline';
|
import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline';
|
||||||
import { StatefulEventContext } from '../../../../../common/components/events_viewer/stateful_event_context';
|
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 { TableId } from '@kbn/securitysolution-data-table';
|
||||||
import { createExpandableFlyoutApiMock } from '../../../../../common/mock/expandable_flyout';
|
import { createExpandableFlyoutApiMock } from '../../../../../common/mock/expandable_flyout';
|
||||||
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||||
|
|
||||||
const mockedTelemetry = createTelemetryServiceMock();
|
|
||||||
const mockOpenRightPanel = jest.fn();
|
const mockOpenRightPanel = jest.fn();
|
||||||
|
|
||||||
jest.mock('@kbn/expandable-flyout');
|
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', () => ({
|
jest.mock('../../../../../common/components/draggables', () => ({
|
||||||
DefaultDraggable: () => <div data-test-subj="DefaultDraggable" />,
|
DefaultDraggable: () => <div data-test-subj="DefaultDraggable" />,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { HostDetailsLink } from '../../../../../common/components/links';
|
||||||
import { DefaultDraggable } from '../../../../../common/components/draggables';
|
import { DefaultDraggable } from '../../../../../common/components/draggables';
|
||||||
import { getEmptyTagValue } from '../../../../../common/components/empty_value';
|
import { getEmptyTagValue } from '../../../../../common/components/empty_value';
|
||||||
import { TruncatableText } from '../../../../../common/components/truncatable_text';
|
import { TruncatableText } from '../../../../../common/components/truncatable_text';
|
||||||
|
import { useIsInSecurityApp } from '../../../../../common/hooks/is_in_security_app';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
contextId: string;
|
contextId: string;
|
||||||
|
@ -45,6 +46,8 @@ const HostNameComponent: React.FC<Props> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { openRightPanel } = useExpandableFlyoutApi();
|
const { openRightPanel } = useExpandableFlyoutApi();
|
||||||
|
|
||||||
|
const isInSecurityApp = useIsInSecurityApp();
|
||||||
|
|
||||||
const eventContext = useContext(StatefulEventContext);
|
const eventContext = useContext(StatefulEventContext);
|
||||||
const hostName = `${value}`;
|
const hostName = `${value}`;
|
||||||
const isInTimelineContext =
|
const isInTimelineContext =
|
||||||
|
@ -58,6 +61,10 @@ const HostNameComponent: React.FC<Props> = ({
|
||||||
onClick();
|
onClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* if and only if renderer is running inside security solution app
|
||||||
|
* we check for event and timeline context
|
||||||
|
* */
|
||||||
if (!eventContext || !isInTimelineContext) {
|
if (!eventContext || !isInTimelineContext) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -85,13 +92,21 @@ const HostNameComponent: React.FC<Props> = ({
|
||||||
Component={Component}
|
Component={Component}
|
||||||
hostName={hostName}
|
hostName={hostName}
|
||||||
isButton={isButton}
|
isButton={isButton}
|
||||||
onClick={isInTimelineContext ? openHostDetailsSidePanel : undefined}
|
onClick={isInTimelineContext || !isInSecurityApp ? openHostDetailsSidePanel : undefined}
|
||||||
title={title}
|
title={title}
|
||||||
>
|
>
|
||||||
<TruncatableText data-test-subj="draggable-truncatable-content">{hostName}</TruncatableText>
|
<TruncatableText data-test-subj="draggable-truncatable-content">{hostName}</TruncatableText>
|
||||||
</HostDetailsLink>
|
</HostDetailsLink>
|
||||||
),
|
),
|
||||||
[Component, hostName, isButton, isInTimelineContext, openHostDetailsSidePanel, title]
|
[
|
||||||
|
Component,
|
||||||
|
hostName,
|
||||||
|
isButton,
|
||||||
|
isInTimelineContext,
|
||||||
|
openHostDetailsSidePanel,
|
||||||
|
title,
|
||||||
|
isInSecurityApp,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return isString(value) && hostName.length > 0 ? (
|
return isString(value) && hostName.length > 0 ? (
|
||||||
|
|
|
@ -12,30 +12,14 @@ import { TestProviders } from '../../../../../common/mock';
|
||||||
import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline';
|
import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline';
|
||||||
import { UserName } from './user_name';
|
import { UserName } from './user_name';
|
||||||
import { StatefulEventContext } from '../../../../../common/components/events_viewer/stateful_event_context';
|
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 { TableId } from '@kbn/securitysolution-data-table';
|
||||||
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||||
import { createExpandableFlyoutApiMock } from '../../../../../common/mock/expandable_flyout';
|
import { createExpandableFlyoutApiMock } from '../../../../../common/mock/expandable_flyout';
|
||||||
|
|
||||||
const mockedTelemetry = createTelemetryServiceMock();
|
|
||||||
const mockOpenRightPanel = jest.fn();
|
const mockOpenRightPanel = jest.fn();
|
||||||
|
|
||||||
jest.mock('@kbn/expandable-flyout');
|
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', () => ({
|
jest.mock('../../../../../common/components/draggables', () => ({
|
||||||
DefaultDraggable: () => <div data-test-subj="DefaultDraggable" />,
|
DefaultDraggable: () => <div data-test-subj="DefaultDraggable" />,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { DefaultDraggable } from '../../../../../common/components/draggables';
|
||||||
import { getEmptyTagValue } from '../../../../../common/components/empty_value';
|
import { getEmptyTagValue } from '../../../../../common/components/empty_value';
|
||||||
import { UserDetailsLink } from '../../../../../common/components/links';
|
import { UserDetailsLink } from '../../../../../common/components/links';
|
||||||
import { TruncatableText } from '../../../../../common/components/truncatable_text';
|
import { TruncatableText } from '../../../../../common/components/truncatable_text';
|
||||||
|
import { useIsInSecurityApp } from '../../../../../common/hooks/is_in_security_app';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
contextId: string;
|
contextId: string;
|
||||||
|
@ -48,6 +49,8 @@ const UserNameComponent: React.FC<Props> = ({
|
||||||
const isInTimelineContext = userName && eventContext?.timelineID;
|
const isInTimelineContext = userName && eventContext?.timelineID;
|
||||||
const { openRightPanel } = useExpandableFlyoutApi();
|
const { openRightPanel } = useExpandableFlyoutApi();
|
||||||
|
|
||||||
|
const isInSecurityApp = useIsInSecurityApp();
|
||||||
|
|
||||||
const openUserDetailsSidePanel = useCallback(
|
const openUserDetailsSidePanel = useCallback(
|
||||||
(e: React.SyntheticEvent) => {
|
(e: React.SyntheticEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -83,13 +86,21 @@ const UserNameComponent: React.FC<Props> = ({
|
||||||
Component={Component}
|
Component={Component}
|
||||||
userName={userName}
|
userName={userName}
|
||||||
isButton={isButton}
|
isButton={isButton}
|
||||||
onClick={isInTimelineContext ? openUserDetailsSidePanel : undefined}
|
onClick={isInTimelineContext || !isInSecurityApp ? openUserDetailsSidePanel : undefined}
|
||||||
title={title}
|
title={title}
|
||||||
>
|
>
|
||||||
<TruncatableText data-test-subj="draggable-truncatable-content">{userName}</TruncatableText>
|
<TruncatableText data-test-subj="draggable-truncatable-content">{userName}</TruncatableText>
|
||||||
</UserDetailsLink>
|
</UserDetailsLink>
|
||||||
),
|
),
|
||||||
[userName, isButton, isInTimelineContext, openUserDetailsSidePanel, Component, title]
|
[
|
||||||
|
userName,
|
||||||
|
isButton,
|
||||||
|
isInTimelineContext,
|
||||||
|
openUserDetailsSidePanel,
|
||||||
|
Component,
|
||||||
|
title,
|
||||||
|
isInSecurityApp,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return isString(value) && userName.length > 0 ? (
|
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 { MapsStartApi } from '@kbn/maps-plugin/public';
|
||||||
import type { IntegrationAssistantPluginStart } from '@kbn/integration-assistant-plugin/public';
|
import type { IntegrationAssistantPluginStart } from '@kbn/integration-assistant-plugin/public';
|
||||||
import type { ServerlessPluginStart } from '@kbn/serverless/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 { ResolverPluginSetup } from './resolver/types';
|
||||||
import type { Inspect } from '../common/search_strategy';
|
import type { Inspect } from '../common/search_strategy';
|
||||||
import type { Detections } from './detections';
|
import type { Detections } from './detections';
|
||||||
|
@ -107,6 +108,7 @@ export interface SetupPlugins {
|
||||||
ml?: MlPluginSetup;
|
ml?: MlPluginSetup;
|
||||||
cases?: CasesPublicSetup;
|
cases?: CasesPublicSetup;
|
||||||
data: DataPublicPluginSetup;
|
data: DataPublicPluginSetup;
|
||||||
|
discoverShared: DiscoverSharedPublicStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,7 +15,11 @@
|
||||||
"public/**/*.json",
|
"public/**/*.json",
|
||||||
"../../../typings/**/*"
|
"../../../typings/**/*"
|
||||||
],
|
],
|
||||||
"exclude": ["target/**/*", "**/cypress/**", "public/management/cypress.config.ts"],
|
"exclude": [
|
||||||
|
"target/**/*",
|
||||||
|
"**/cypress/**",
|
||||||
|
"public/management/cypress.config.ts"
|
||||||
|
],
|
||||||
"kbn_references": [
|
"kbn_references": [
|
||||||
"@kbn/core",
|
"@kbn/core",
|
||||||
{
|
{
|
||||||
|
@ -228,6 +232,7 @@
|
||||||
"@kbn/core-lifecycle-server",
|
"@kbn/core-lifecycle-server",
|
||||||
"@kbn/core-user-profile-common",
|
"@kbn/core-user-profile-common",
|
||||||
"@kbn/langchain",
|
"@kbn/langchain",
|
||||||
|
"@kbn/discover-shared-plugin",
|
||||||
"@kbn/react-hooks",
|
"@kbn/react-hooks",
|
||||||
"@kbn/index-adapter",
|
"@kbn/index-adapter",
|
||||||
"@kbn/core-http-server-utils"
|
"@kbn/core-http-server-utils"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue