mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Security Solution] Display security job friendly name on Explore and Investigation pages (#148780)
issue: https://github.com/elastic/kibana/issues/146772 ## Summary To help our users understand what pre-built jobs do, we will display a human-readable job name inside security solutions when available on the job configuration. The job name is configured inside `job.custom_settings.security_app_display_name`. This change will affect the following pages: - Entity analytics page: Notable anomalies sections - User page: Anomalies tab - job filter, and job column - Host page: Anomalies tab - job filter, and job column - Network page: Anomalies tab - job filter, and job column - User details page and flyout: Anomaly popover (it shows when you click on the information icon) - Host details page and flyout: Anomaly popover (it shows when you click on the information icon) - Network details page and flyout: Anomaly popover (it shows when you click on the information icon) - ML Job Setting panel: Available on the Alerts page **The User/Host/Network flyout is displayed in Alerts and Timeline** ### Not included (follow-up PR) * Rules details page * Rules creation page ### Before <img width="600" alt="Screenshot 2023-01-12 at 10 08 32" src="https://user-images.githubusercontent.com/1490444/212025037-d2bec806-3439-4758-b01c-532957faff2b.png"> <img width="600" alt="Screenshot 2023-01-12 at 10 07 55" src="https://user-images.githubusercontent.com/1490444/212025045-2892b2e1-290c-459a-bb39-cf40033ad334.png"> <img width="600" alt="Screenshot 2023-01-12 at 10 06 59" src="https://user-images.githubusercontent.com/1490444/212025057-f2fe612f-5718-42b7-9e47-b7bacb513539.png"> <img width="600" alt="Screenshot 2023-01-12 at 10 10 54" src="https://user-images.githubusercontent.com/1490444/212025877-b4a0698c-d716-47b9-8095-72ca2ea19064.png"> ### After <img width="600" alt="Screenshot 2023-01-12 at 14 34 25" src="https://user-images.githubusercontent.com/1490444/212096616-0f144a08-482e-4ab6-a0ea-88198d199531.png"> <img width="600" alt="Screenshot 2023-01-12 at 14 34 47" src="https://user-images.githubusercontent.com/1490444/212096627-92dce2aa-1ea0-409b-bcd2-ce13a7031288.png"> <img width="600" alt="Screenshot 2023-01-12 at 14 37 06" src="https://user-images.githubusercontent.com/1490444/212096634-6f23ed16-4995-4764-9558-25028a6376bc.png"> <img width="600" alt="Screenshot 2023-01-12 at 15 44 53" src="https://user-images.githubusercontent.com/1490444/212097151-be2f6198-7f56-4a78-8553-eb25e65909cf.png"> ### 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: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
150b447c9d
commit
f1934a3a27
53 changed files with 579 additions and 199 deletions
|
@ -5,14 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useInstalledSecurityJobsIds } from '../hooks/use_installed_security_jobs';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useInstalledSecurityJobNameById } from '../hooks/use_installed_security_jobs';
|
||||
import type { InfluencerInput, Anomalies, CriteriaFields } from '../types';
|
||||
import { useAnomaliesTableData } from './use_anomalies_table_data';
|
||||
|
||||
interface ChildrenArgs {
|
||||
isLoadingAnomaliesData: boolean;
|
||||
anomaliesData: Anomalies | null;
|
||||
jobNameById: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
@ -26,7 +27,9 @@ interface Props {
|
|||
|
||||
export const AnomalyTableProvider = React.memo<Props>(
|
||||
({ influencers, startDate, endDate, children, criteriaFields, skip }) => {
|
||||
const { jobIds } = useInstalledSecurityJobsIds();
|
||||
const { jobNameById } = useInstalledSecurityJobNameById();
|
||||
const jobIds = useMemo(() => Object.keys(jobNameById), [jobNameById]);
|
||||
|
||||
const [isLoadingAnomaliesData, anomaliesData] = useAnomaliesTableData({
|
||||
criteriaFields,
|
||||
influencers,
|
||||
|
@ -36,7 +39,7 @@ export const AnomalyTableProvider = React.memo<Props>(
|
|||
jobIds,
|
||||
aggregationInterval: 'auto',
|
||||
});
|
||||
return <>{children({ isLoadingAnomaliesData, anomaliesData })}</>;
|
||||
return <>{children({ isLoadingAnomaliesData, anomaliesData, jobNameById })}</>;
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ describe('useNotableAnomaliesSearch', () => {
|
|||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(result.current.data.length).toEqual(6);
|
||||
expect(result.current.data.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('calls notableAnomaliesSearch when skip is false', async () => {
|
||||
|
@ -116,6 +116,63 @@ describe('useNotableAnomaliesSearch', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('returns jobs sorted by name', async () => {
|
||||
await act(async () => {
|
||||
const firstJobId = 'v3_windows_anomalous_script';
|
||||
const secondJobId = 'auth_rare_source_ip_for_a_user';
|
||||
const fistJobCount = { key: firstJobId, doc_count: 99 };
|
||||
const secondJobCount = { key: secondJobId, doc_count: 99 };
|
||||
const firstJobSecurityName = '0000001';
|
||||
const secondJobSecurityName = '0000002';
|
||||
const firstJob = {
|
||||
id: firstJobId,
|
||||
jobState: 'started',
|
||||
datafeedState: 'started',
|
||||
customSettings: {
|
||||
security_app_display_name: firstJobSecurityName,
|
||||
},
|
||||
};
|
||||
const secondJob = {
|
||||
id: secondJobId,
|
||||
jobState: 'started',
|
||||
datafeedState: 'started',
|
||||
customSettings: {
|
||||
security_app_display_name: secondJobSecurityName,
|
||||
},
|
||||
};
|
||||
|
||||
mockNotableAnomaliesSearch.mockResolvedValue({
|
||||
aggregations: { number_of_anomalies: { buckets: [fistJobCount, secondJobCount] } },
|
||||
});
|
||||
|
||||
mockUseSecurityJobs.mockReturnValue({
|
||||
loading: false,
|
||||
isMlAdmin: true,
|
||||
jobs: [firstJob, secondJob],
|
||||
refetch: useSecurityJobsRefetch,
|
||||
});
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() => useNotableAnomaliesSearch({ skip: false, from, to }),
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
await waitForNextUpdate();
|
||||
await waitForNextUpdate();
|
||||
|
||||
const names = result.current.data.map(({ name }) => name);
|
||||
expect(names).toEqual([
|
||||
firstJobSecurityName,
|
||||
secondJobSecurityName,
|
||||
'packetbeat_dns_tunneling',
|
||||
'packetbeat_rare_dns_question',
|
||||
'packetbeat_rare_server_domain',
|
||||
'suspicious_login_activity',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not throw error when aggregations is undefined', async () => {
|
||||
await act(async () => {
|
||||
mockNotableAnomaliesSearch.mockResolvedValue({});
|
||||
|
@ -132,40 +189,6 @@ describe('useNotableAnomaliesSearch', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('returns uninstalled jobs', async () => {
|
||||
mockUseSecurityJobs.mockReturnValue({
|
||||
loading: false,
|
||||
isMlAdmin: true,
|
||||
jobs: [],
|
||||
refetch: useSecurityJobsRefetch,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
mockNotableAnomaliesSearch.mockResolvedValue({
|
||||
aggregations: { number_of_anomalies: { buckets: [] } },
|
||||
});
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() => useNotableAnomaliesSearch({ skip: false, from, to }),
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
await waitForNextUpdate();
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.data).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
count: 0,
|
||||
name: job.id,
|
||||
job: undefined,
|
||||
entity: AnomalyEntity.Host,
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns jobs with custom job ids', async () => {
|
||||
const customJobId = `test_${jobId}`;
|
||||
const jobCount = { key: customJobId, doc_count: 99 };
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { filter, head, orderBy, pipe, has } from 'lodash/fp';
|
||||
import { filter, head, orderBy, pipe, has, sortBy } from 'lodash/fp';
|
||||
|
||||
import { DEFAULT_ANOMALY_SCORE } from '../../../../../common/constants';
|
||||
import * as i18n from './translations';
|
||||
|
@ -26,7 +26,7 @@ export enum AnomalyEntity {
|
|||
}
|
||||
|
||||
export interface AnomaliesCount {
|
||||
name: NotableAnomaliesJobId;
|
||||
name: NotableAnomaliesJobId | string;
|
||||
count: number;
|
||||
entity: AnomalyEntity;
|
||||
job?: SecurityJob;
|
||||
|
@ -47,7 +47,7 @@ export const useNotableAnomaliesSearch = ({
|
|||
data: AnomaliesCount[];
|
||||
refetch: inputsModel.Refetch;
|
||||
} => {
|
||||
const [data, setData] = useState<AnomaliesCount[]>(formatResultData([], []));
|
||||
const [data, setData] = useState<AnomaliesCount[]>([]);
|
||||
|
||||
const {
|
||||
loading: jobsLoading,
|
||||
|
@ -131,18 +131,20 @@ function formatResultData(
|
|||
}>,
|
||||
notableAnomaliesJobs: SecurityJob[]
|
||||
): AnomaliesCount[] {
|
||||
return NOTABLE_ANOMALIES_IDS.map((notableJobId) => {
|
||||
const unsortedAnomalies: AnomaliesCount[] = NOTABLE_ANOMALIES_IDS.map((notableJobId) => {
|
||||
const job = findJobWithId(notableJobId)(notableAnomaliesJobs);
|
||||
const bucket = buckets.find(({ key }) => key === job?.id);
|
||||
const hasUserName = has("entity.hits.hits[0]._source['user.name']", bucket);
|
||||
|
||||
return {
|
||||
name: notableJobId,
|
||||
name: job?.customSettings?.security_app_display_name ?? notableJobId,
|
||||
count: bucket?.doc_count ?? 0,
|
||||
entity: hasUserName ? AnomalyEntity.User : AnomalyEntity.Host,
|
||||
job,
|
||||
};
|
||||
});
|
||||
|
||||
return sortBy(['name'], unsortedAnomalies);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -72,3 +72,18 @@ export const useInstalledSecurityJobsIds = () => {
|
|||
|
||||
return { jobIds, loading };
|
||||
};
|
||||
|
||||
export const useInstalledSecurityJobNameById = () => {
|
||||
const { jobs, loading } = useInstalledSecurityJobs();
|
||||
|
||||
const jobNameById = useMemo(
|
||||
() =>
|
||||
jobs.reduce<Record<string, string | undefined>>((acc, job) => {
|
||||
acc[job.id] = job.customSettings?.security_app_display_name;
|
||||
return acc;
|
||||
}, {}),
|
||||
[jobs]
|
||||
);
|
||||
|
||||
return { jobNameById, loading };
|
||||
};
|
||||
|
|
|
@ -48,7 +48,7 @@ export const ExplorerLink: React.FC<ExplorerLinkProps> = ({
|
|||
if (!explorerUrl) return null;
|
||||
|
||||
return (
|
||||
<EuiLink href={explorerUrl} target="_blank">
|
||||
<EuiLink href={explorerUrl} target="_blank" data-test-subj={`explorer-link-${score.jobId}`}>
|
||||
{linkName}
|
||||
</EuiLink>
|
||||
);
|
||||
|
|
|
@ -11,6 +11,7 @@ exports[`anomaly_scores renders correctly against snapshot 1`] = `
|
|||
index={0}
|
||||
interval="day"
|
||||
jobKey="job-1-16.193669439507826-process.name-du"
|
||||
jobName="job-1"
|
||||
key="job-1-16.193669439507826-process.name-du"
|
||||
narrowDateRange={[MockFunction]}
|
||||
score={
|
||||
|
@ -86,6 +87,7 @@ exports[`anomaly_scores renders correctly against snapshot 1`] = `
|
|||
index={1}
|
||||
interval="day"
|
||||
jobKey="job-2-16.193669439507826-process.name-ls"
|
||||
jobName="job-2"
|
||||
key="job-2-16.193669439507826-process.name-ls"
|
||||
narrowDateRange={[MockFunction]}
|
||||
score={
|
||||
|
|
|
@ -40,6 +40,7 @@ describe('anomaly_scores', () => {
|
|||
score={anomalies.anomalies[0]}
|
||||
interval="day"
|
||||
narrowDateRange={narrowDateRange}
|
||||
jobName={'job-1'}
|
||||
/>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
@ -55,6 +56,7 @@ describe('anomaly_scores', () => {
|
|||
score={anomalies.anomalies[0]}
|
||||
interval="day"
|
||||
narrowDateRange={narrowDateRange}
|
||||
jobName={'job-1'}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -71,6 +73,7 @@ describe('anomaly_scores', () => {
|
|||
score={anomalies.anomalies[0]}
|
||||
interval="day"
|
||||
narrowDateRange={narrowDateRange}
|
||||
jobName={'job-1'}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
|
|
@ -21,6 +21,7 @@ interface Args {
|
|||
index?: number;
|
||||
score: Anomaly;
|
||||
interval: string;
|
||||
jobName: string;
|
||||
}
|
||||
|
||||
const Icon = styled(EuiIcon)`
|
||||
|
@ -38,6 +39,7 @@ export const AnomalyScoreComponent = ({
|
|||
score,
|
||||
interval,
|
||||
narrowDateRange,
|
||||
jobName,
|
||||
}: Args): JSX.Element => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
return (
|
||||
|
@ -61,7 +63,14 @@ export const AnomalyScoreComponent = ({
|
|||
>
|
||||
<EuiDescriptionList
|
||||
data-test-subj="anomaly-description-list"
|
||||
listItems={createDescriptionList(score, startDate, endDate, interval, narrowDateRange)}
|
||||
listItems={createDescriptionList(
|
||||
score,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange,
|
||||
jobName
|
||||
)}
|
||||
/>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -39,6 +39,7 @@ describe('anomaly_scores', () => {
|
|||
endDate={endDate}
|
||||
isLoading={false}
|
||||
narrowDateRange={narrowDateRange}
|
||||
jobNameById={{}}
|
||||
/>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
@ -53,6 +54,7 @@ describe('anomaly_scores', () => {
|
|||
endDate={endDate}
|
||||
isLoading={true}
|
||||
narrowDateRange={narrowDateRange}
|
||||
jobNameById={{}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -68,6 +70,7 @@ describe('anomaly_scores', () => {
|
|||
endDate={endDate}
|
||||
isLoading={false}
|
||||
narrowDateRange={narrowDateRange}
|
||||
jobNameById={{}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -83,6 +86,7 @@ describe('anomaly_scores', () => {
|
|||
endDate={endDate}
|
||||
isLoading={false}
|
||||
narrowDateRange={narrowDateRange}
|
||||
jobNameById={{}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -99,6 +103,7 @@ describe('anomaly_scores', () => {
|
|||
endDate={endDate}
|
||||
isLoading={false}
|
||||
narrowDateRange={narrowDateRange}
|
||||
jobNameById={{}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -119,6 +124,7 @@ describe('anomaly_scores', () => {
|
|||
endDate={endDate}
|
||||
isLoading={false}
|
||||
narrowDateRange={narrowDateRange}
|
||||
jobNameById={{}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -134,6 +140,7 @@ describe('anomaly_scores', () => {
|
|||
endDate={endDate}
|
||||
isLoading={false}
|
||||
narrowDateRange={narrowDateRange}
|
||||
jobNameById={{}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
|
|
@ -19,6 +19,7 @@ interface Args {
|
|||
isLoading: boolean;
|
||||
narrowDateRange: NarrowDateRange;
|
||||
limit?: number;
|
||||
jobNameById: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
export const createJobKey = (score: Anomaly): string =>
|
||||
|
@ -31,6 +32,7 @@ export const AnomalyScoresComponent = ({
|
|||
isLoading,
|
||||
narrowDateRange,
|
||||
limit,
|
||||
jobNameById,
|
||||
}: Args): JSX.Element => {
|
||||
if (isLoading) {
|
||||
return <EuiLoadingSpinner data-test-subj="anomaly-score-spinner" size="m" />;
|
||||
|
@ -50,6 +52,7 @@ export const AnomalyScoresComponent = ({
|
|||
endDate={endDate}
|
||||
index={index}
|
||||
score={score}
|
||||
jobName={jobNameById[score.jobId] ?? score.jobId}
|
||||
interval={anomalies.interval}
|
||||
narrowDateRange={narrowDateRange}
|
||||
/>
|
||||
|
|
|
@ -29,7 +29,8 @@ export const createDescriptionList = (
|
|||
startDate: string,
|
||||
endDate: string,
|
||||
interval: string,
|
||||
narrowDateRange: NarrowDateRange
|
||||
narrowDateRange: NarrowDateRange,
|
||||
jobName: string
|
||||
): DescriptionList[] => {
|
||||
const descriptionList: DescriptionList[] = [
|
||||
{
|
||||
|
@ -50,7 +51,7 @@ export const createDescriptionList = (
|
|||
),
|
||||
description: (
|
||||
<EuiFlexGroup direction="column" gutterSize="none" responsive={false}>
|
||||
<EuiFlexItem grow={false}>{score.jobId}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{jobName}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ExplorerLink
|
||||
score={score}
|
||||
|
|
|
@ -33,7 +33,8 @@ describe('create_description_list', () => {
|
|||
startDate,
|
||||
endDate,
|
||||
'hours',
|
||||
narrowDateRange
|
||||
narrowDateRange,
|
||||
'job-1'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
@ -48,7 +49,8 @@ describe('create_description_list', () => {
|
|||
startDate,
|
||||
endDate,
|
||||
'hours',
|
||||
narrowDateRange
|
||||
narrowDateRange,
|
||||
'job-1'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
@ -69,7 +71,8 @@ describe('create_description_list', () => {
|
|||
startDate,
|
||||
endDate,
|
||||
'hours',
|
||||
narrowDateRange
|
||||
narrowDateRange,
|
||||
'job-1'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
@ -129,7 +132,8 @@ describe('create_description_list', () => {
|
|||
startDate,
|
||||
endDate,
|
||||
'hours',
|
||||
narrowDateRange
|
||||
narrowDateRange,
|
||||
'job-1'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mount } from 'enzyme';
|
||||
import { AnomaliesHostTable } from './anomalies_host_table';
|
||||
import { TestProviders } from '../../../mock';
|
||||
import React from 'react';
|
||||
|
@ -13,27 +12,66 @@ import { useQueryToggle } from '../../../containers/query_toggle';
|
|||
import { useAnomaliesTableData } from '../anomaly/use_anomalies_table_data';
|
||||
import { HostsType } from '../../../../explore/hosts/store/model';
|
||||
import { hasMlUserPermissions } from '../../../../../common/machine_learning/has_ml_user_permissions';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { useInstalledSecurityJobNameById } from '../hooks/use_installed_security_jobs';
|
||||
import { mockAnomalies } from '../mock';
|
||||
import { useMlHref } from '@kbn/ml-plugin/public';
|
||||
|
||||
jest.mock('../../../containers/query_toggle');
|
||||
jest.mock('../anomaly/use_anomalies_table_data');
|
||||
jest.mock('../../../../../common/machine_learning/has_ml_user_permissions');
|
||||
jest.mock('../hooks/use_installed_security_jobs');
|
||||
jest.mock('@kbn/ml-plugin/public');
|
||||
|
||||
const mockUseQueryToggle = useQueryToggle as jest.Mock;
|
||||
const mockUseAnomaliesTableData = useAnomaliesTableData as jest.Mock;
|
||||
const mockUseInstalledSecurityJobNameById = useInstalledSecurityJobNameById as jest.Mock;
|
||||
const mockUseMlHref = useMlHref as jest.Mock;
|
||||
|
||||
const mockSetToggle = jest.fn();
|
||||
|
||||
(hasMlUserPermissions as jest.Mock).mockReturnValue(true);
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle });
|
||||
mockUseMlHref.mockReturnValue('http://test');
|
||||
mockUseInstalledSecurityJobNameById.mockReturnValue({
|
||||
loading: false,
|
||||
jobNameById: {},
|
||||
});
|
||||
const testProps = {
|
||||
startDate: '2019-07-17T20:00:00.000Z',
|
||||
endDate: '2019-07-18T20:00:00.000Z',
|
||||
narrowDateRange: jest.fn(),
|
||||
skip: false,
|
||||
type: HostsType.page,
|
||||
};
|
||||
|
||||
describe('Anomalies host table', () => {
|
||||
it('renders job name when available', () => {
|
||||
const anomaly = mockAnomalies.anomalies[0];
|
||||
const jobName = 'job_name';
|
||||
|
||||
mockUseAnomaliesTableData.mockReturnValue([
|
||||
false,
|
||||
{
|
||||
anomalies: [anomaly],
|
||||
interval: '10',
|
||||
},
|
||||
]);
|
||||
mockUseInstalledSecurityJobNameById.mockReturnValue({
|
||||
loading: false,
|
||||
jobNameById: { [anomaly.jobId]: jobName },
|
||||
});
|
||||
|
||||
const { getByTestId } = render(<AnomaliesHostTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(getByTestId(`explorer-link-${anomaly.jobId}`).textContent).toContain(jobName);
|
||||
});
|
||||
|
||||
describe('toggle query', () => {
|
||||
const mockUseQueryToggle = useQueryToggle as jest.Mock;
|
||||
const mockUseAnomaliesTableData = useAnomaliesTableData as jest.Mock;
|
||||
const mockSetToggle = jest.fn();
|
||||
const testProps = {
|
||||
startDate: '2019-07-17T20:00:00.000Z',
|
||||
endDate: '2019-07-18T20:00:00.000Z',
|
||||
narrowDateRange: jest.fn(),
|
||||
skip: false,
|
||||
type: HostsType.page,
|
||||
};
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(hasMlUserPermissions as jest.Mock).mockReturnValue(true);
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle });
|
||||
mockUseAnomaliesTableData.mockReturnValue([
|
||||
false,
|
||||
{
|
||||
|
@ -44,42 +82,43 @@ describe('Anomalies host table', () => {
|
|||
});
|
||||
|
||||
test('toggleQuery updates toggleStatus', () => {
|
||||
const wrapper = mount(<AnomaliesHostTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
const { getByTestId } = render(<AnomaliesHostTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(false);
|
||||
wrapper.find('[data-test-subj="query-toggle-header"]').first().simulate('click');
|
||||
fireEvent.click(getByTestId('query-toggle-header'));
|
||||
|
||||
expect(mockSetToggle).toBeCalledWith(false);
|
||||
expect(mockUseAnomaliesTableData.mock.calls[1][0].skip).toEqual(true);
|
||||
});
|
||||
|
||||
test('toggleStatus=true, do not skip', () => {
|
||||
mount(<AnomaliesHostTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
render(<AnomaliesHostTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(false);
|
||||
});
|
||||
|
||||
test('toggleStatus=true, render components', () => {
|
||||
const wrapper = mount(<AnomaliesHostTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
const { queryByTestId } = render(<AnomaliesHostTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(wrapper.find('[data-test-subj="host-anomalies-table"]').exists()).toBe(true);
|
||||
expect(queryByTestId('host-anomalies-table')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('toggleStatus=false, do not render components', () => {
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
|
||||
const wrapper = mount(<AnomaliesHostTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
const { queryByTestId } = render(<AnomaliesHostTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(wrapper.find('[data-test-subj="host-anomalies-table"]').exists()).toBe(false);
|
||||
expect(queryByTestId('host-anomalies-table')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('toggleStatus=false, skip', () => {
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
|
||||
mount(<AnomaliesHostTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
render(<AnomaliesHostTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(true);
|
||||
|
|
|
@ -22,7 +22,7 @@ import { BasicTable } from './basic_table';
|
|||
import { getCriteriaFromHostType } from '../criteria/get_criteria_from_host_type';
|
||||
import { Panel } from '../../panel';
|
||||
import { useQueryToggle } from '../../../containers/query_toggle';
|
||||
import { useInstalledSecurityJobsIds } from '../hooks/use_installed_security_jobs';
|
||||
import { useInstalledSecurityJobNameById } from '../hooks/use_installed_security_jobs';
|
||||
import { useDeepEqualSelector } from '../../../hooks/use_selector';
|
||||
import type { State } from '../../../store';
|
||||
import { JobIdFilter } from './job_id_filter';
|
||||
|
@ -59,13 +59,13 @@ const AnomaliesHostTableComponent: React.FC<AnomaliesHostTableProps> = ({
|
|||
[setQuerySkip, setToggleStatus]
|
||||
);
|
||||
|
||||
const { jobIds, loading: loadingJobs } = useInstalledSecurityJobsIds();
|
||||
const { jobNameById, loading: loadingJobs } = useInstalledSecurityJobNameById();
|
||||
const jobIds = useMemo(() => Object.keys(jobNameById), [jobNameById]);
|
||||
|
||||
const getAnomaliesHostsTableFilterQuerySelector = useMemo(
|
||||
() => hostsSelectors.hostsAnomaliesJobIdFilterSelector(),
|
||||
[]
|
||||
);
|
||||
|
||||
const selectedJobIds = useDeepEqualSelector((state: State) =>
|
||||
getAnomaliesHostsTableFilterQuerySelector(state, type)
|
||||
);
|
||||
|
@ -115,7 +115,7 @@ const AnomaliesHostTableComponent: React.FC<AnomaliesHostTableProps> = ({
|
|||
aggregationInterval: selectedInterval,
|
||||
});
|
||||
|
||||
const hosts = convertAnomaliesToHosts(tableData, hostName);
|
||||
const hosts = convertAnomaliesToHosts(tableData, jobNameById, hostName);
|
||||
|
||||
const columns = getAnomaliesHostTableColumnsCurated(type, startDate, endDate);
|
||||
const pagination = {
|
||||
|
@ -151,6 +151,7 @@ const AnomaliesHostTableComponent: React.FC<AnomaliesHostTableProps> = ({
|
|||
onSelect={onSelectJobId}
|
||||
selectedJobIds={selectedJobIds}
|
||||
jobIds={jobIds}
|
||||
jobNameById={jobNameById}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mount } from 'enzyme';
|
||||
import { AnomaliesNetworkTable } from './anomalies_network_table';
|
||||
import { TestProviders } from '../../../mock';
|
||||
import React from 'react';
|
||||
|
@ -14,28 +13,72 @@ import { useAnomaliesTableData } from '../anomaly/use_anomalies_table_data';
|
|||
import { NetworkType } from '../../../../explore/network/store/model';
|
||||
import { hasMlUserPermissions } from '../../../../../common/machine_learning/has_ml_user_permissions';
|
||||
import { FlowTarget } from '../../../../../common/search_strategy';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { mockAnomalies } from '../mock';
|
||||
import { useInstalledSecurityJobNameById } from '../hooks/use_installed_security_jobs';
|
||||
import { useMlHref } from '@kbn/ml-plugin/public';
|
||||
|
||||
jest.mock('../../../containers/query_toggle');
|
||||
jest.mock('../anomaly/use_anomalies_table_data');
|
||||
jest.mock('../../../../../common/machine_learning/has_ml_user_permissions');
|
||||
jest.mock('../hooks/use_installed_security_jobs');
|
||||
jest.mock('@kbn/ml-plugin/public');
|
||||
|
||||
const mockUseQueryToggle = useQueryToggle as jest.Mock;
|
||||
const mockUseAnomaliesTableData = useAnomaliesTableData as jest.Mock;
|
||||
const mockUseInstalledSecurityJobNameById = useInstalledSecurityJobNameById as jest.Mock;
|
||||
const mockUseMlHref = useMlHref as jest.Mock;
|
||||
|
||||
const mockSetToggle = jest.fn();
|
||||
|
||||
(hasMlUserPermissions as jest.Mock).mockReturnValue(true);
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle });
|
||||
mockUseMlHref.mockReturnValue('http://test');
|
||||
mockUseInstalledSecurityJobNameById.mockReturnValue({
|
||||
loading: false,
|
||||
jobNameById: {},
|
||||
});
|
||||
|
||||
const testProps = {
|
||||
startDate: '2019-07-17T20:00:00.000Z',
|
||||
endDate: '2019-07-18T20:00:00.000Z',
|
||||
flowTarget: FlowTarget.destination,
|
||||
narrowDateRange: jest.fn(),
|
||||
skip: false,
|
||||
type: NetworkType.page,
|
||||
};
|
||||
|
||||
describe('Anomalies network table', () => {
|
||||
describe('toggle query', () => {
|
||||
const mockUseQueryToggle = useQueryToggle as jest.Mock;
|
||||
const mockUseAnomaliesTableData = useAnomaliesTableData as jest.Mock;
|
||||
const mockSetToggle = jest.fn();
|
||||
const testProps = {
|
||||
startDate: '2019-07-17T20:00:00.000Z',
|
||||
endDate: '2019-07-18T20:00:00.000Z',
|
||||
flowTarget: FlowTarget.destination,
|
||||
narrowDateRange: jest.fn(),
|
||||
skip: false,
|
||||
type: NetworkType.page,
|
||||
it('renders job name when available', () => {
|
||||
const anomaly = {
|
||||
...mockAnomalies.anomalies[0],
|
||||
entityValue: '127.0.0.1',
|
||||
entityName: 'source.ip',
|
||||
};
|
||||
const jobName = 'job_name';
|
||||
|
||||
mockUseAnomaliesTableData.mockReturnValue([
|
||||
false,
|
||||
{
|
||||
anomalies: [anomaly],
|
||||
interval: '10',
|
||||
},
|
||||
]);
|
||||
mockUseInstalledSecurityJobNameById.mockReturnValue({
|
||||
loading: false,
|
||||
jobNameById: { [anomaly.jobId]: jobName },
|
||||
});
|
||||
|
||||
const { getByTestId } = render(<AnomaliesNetworkTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(getByTestId(`explorer-link-${anomaly.jobId}`).textContent).toContain(jobName);
|
||||
});
|
||||
|
||||
describe('toggle query', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(hasMlUserPermissions as jest.Mock).mockReturnValue(true);
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle });
|
||||
mockUseAnomaliesTableData.mockReturnValue([
|
||||
false,
|
||||
{
|
||||
|
@ -46,42 +89,42 @@ describe('Anomalies network table', () => {
|
|||
});
|
||||
|
||||
test('toggleQuery updates toggleStatus', () => {
|
||||
const wrapper = mount(<AnomaliesNetworkTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
const { getByTestId } = render(<AnomaliesNetworkTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(false);
|
||||
wrapper.find('[data-test-subj="query-toggle-header"]').first().simulate('click');
|
||||
fireEvent.click(getByTestId('query-toggle-header'));
|
||||
expect(mockSetToggle).toBeCalledWith(false);
|
||||
expect(mockUseAnomaliesTableData.mock.calls[1][0].skip).toEqual(true);
|
||||
});
|
||||
|
||||
test('toggleStatus=true, do not skip', () => {
|
||||
mount(<AnomaliesNetworkTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
render(<AnomaliesNetworkTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(false);
|
||||
});
|
||||
|
||||
test('toggleStatus=true, render components', () => {
|
||||
const wrapper = mount(<AnomaliesNetworkTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
const { queryByTestId } = render(<AnomaliesNetworkTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(wrapper.find('[data-test-subj="network-anomalies-table"]').exists()).toBe(true);
|
||||
expect(queryByTestId('network-anomalies-table')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('toggleStatus=false, do not render components', () => {
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
|
||||
const wrapper = mount(<AnomaliesNetworkTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
const { queryByTestId } = render(<AnomaliesNetworkTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(wrapper.find('[data-test-subj="network-anomalies-table"]').exists()).toBe(false);
|
||||
expect(queryByTestId('network-anomalies-table')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('toggleStatus=false, skip', () => {
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
|
||||
mount(<AnomaliesNetworkTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
render(<AnomaliesNetworkTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(true);
|
||||
|
|
|
@ -22,7 +22,7 @@ import { BasicTable } from './basic_table';
|
|||
import { getCriteriaFromNetworkType } from '../criteria/get_criteria_from_network_type';
|
||||
import { Panel } from '../../panel';
|
||||
import { useQueryToggle } from '../../../containers/query_toggle';
|
||||
import { useInstalledSecurityJobsIds } from '../hooks/use_installed_security_jobs';
|
||||
import { useInstalledSecurityJobNameById } from '../hooks/use_installed_security_jobs';
|
||||
import { useDeepEqualSelector } from '../../../hooks/use_selector';
|
||||
import type { State } from '../../../store';
|
||||
import { JobIdFilter } from './job_id_filter';
|
||||
|
@ -60,7 +60,8 @@ const AnomaliesNetworkTableComponent: React.FC<AnomaliesNetworkTableProps> = ({
|
|||
[setQuerySkip, setToggleStatus]
|
||||
);
|
||||
|
||||
const { jobIds, loading: loadingJobs } = useInstalledSecurityJobsIds();
|
||||
const { jobNameById, loading: loadingJobs } = useInstalledSecurityJobNameById();
|
||||
const jobIds = useMemo(() => Object.keys(jobNameById), [jobNameById]);
|
||||
|
||||
const getAnomaliesUserTableFilterQuerySelector = useMemo(
|
||||
() => networkSelectors.networkAnomaliesJobIdFilterSelector(),
|
||||
|
@ -113,7 +114,7 @@ const AnomaliesNetworkTableComponent: React.FC<AnomaliesNetworkTableProps> = ({
|
|||
aggregationInterval: selectedInterval,
|
||||
});
|
||||
|
||||
const networks = convertAnomaliesToNetwork(tableData, ip);
|
||||
const networks = convertAnomaliesToNetwork(tableData, jobNameById, ip);
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(type, startDate, endDate, flowTarget);
|
||||
const pagination = {
|
||||
initialPageIndex: 0,
|
||||
|
@ -148,6 +149,7 @@ const AnomaliesNetworkTableComponent: React.FC<AnomaliesNetworkTableProps> = ({
|
|||
onSelect={onSelectJobId}
|
||||
selectedJobIds={selectedJobIds}
|
||||
jobIds={jobIds}
|
||||
jobNameById={jobNameById}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mount } from 'enzyme';
|
||||
import { AnomaliesUserTable } from './anomalies_user_table';
|
||||
import { TestProviders } from '../../../mock';
|
||||
import React from 'react';
|
||||
|
@ -13,28 +11,80 @@ import { useQueryToggle } from '../../../containers/query_toggle';
|
|||
import { useAnomaliesTableData } from '../anomaly/use_anomalies_table_data';
|
||||
import { UsersType } from '../../../../explore/users/store/model';
|
||||
import { hasMlUserPermissions } from '../../../../../common/machine_learning/has_ml_user_permissions';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { useInstalledSecurityJobNameById } from '../hooks/use_installed_security_jobs';
|
||||
import { mockAnomalies } from '../mock';
|
||||
import { useMlHref } from '@kbn/ml-plugin/public';
|
||||
|
||||
jest.mock('../../../containers/query_toggle');
|
||||
jest.mock('../anomaly/use_anomalies_table_data');
|
||||
jest.mock('../../../../../common/machine_learning/has_ml_user_permissions');
|
||||
jest.mock('../hooks/use_installed_security_jobs');
|
||||
jest.mock('@kbn/ml-plugin/public');
|
||||
|
||||
const mockUseQueryToggle = useQueryToggle as jest.Mock;
|
||||
const mockUseAnomaliesTableData = useAnomaliesTableData as jest.Mock;
|
||||
const mockUseInstalledSecurityJobNameById = useInstalledSecurityJobNameById as jest.Mock;
|
||||
const mockUseMlHref = useMlHref as jest.Mock;
|
||||
|
||||
const mockSetToggle = jest.fn();
|
||||
|
||||
(hasMlUserPermissions as jest.Mock).mockReturnValue(true);
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle });
|
||||
mockUseMlHref.mockReturnValue('http://test');
|
||||
mockUseInstalledSecurityJobNameById.mockReturnValue({
|
||||
loading: false,
|
||||
jobNameById: {},
|
||||
});
|
||||
|
||||
const userName = 'cool_guy';
|
||||
const testProps = {
|
||||
startDate: '2019-07-17T20:00:00.000Z',
|
||||
endDate: '2019-07-18T20:00:00.000Z',
|
||||
narrowDateRange: jest.fn(),
|
||||
userName,
|
||||
skip: false,
|
||||
type: UsersType.page,
|
||||
};
|
||||
|
||||
describe('Anomalies user table', () => {
|
||||
describe('toggle query', () => {
|
||||
const mockUseQueryToggle = useQueryToggle as jest.Mock;
|
||||
const mockUseAnomaliesTableData = useAnomaliesTableData as jest.Mock;
|
||||
const mockSetToggle = jest.fn();
|
||||
const testProps = {
|
||||
startDate: '2019-07-17T20:00:00.000Z',
|
||||
endDate: '2019-07-18T20:00:00.000Z',
|
||||
narrowDateRange: jest.fn(),
|
||||
userName: 'coolguy',
|
||||
skip: false,
|
||||
type: UsersType.page,
|
||||
it('renders job name when available', () => {
|
||||
const anomaly = {
|
||||
...mockAnomalies.anomalies[0],
|
||||
entityValue: userName,
|
||||
entityName: 'user.name',
|
||||
};
|
||||
const jobName = 'job_name';
|
||||
|
||||
mockUseAnomaliesTableData.mockReturnValue([
|
||||
false,
|
||||
{
|
||||
anomalies: [anomaly],
|
||||
interval: '10',
|
||||
},
|
||||
]);
|
||||
|
||||
mockUseAnomaliesTableData.mockReturnValue([
|
||||
false,
|
||||
{
|
||||
anomalies: [anomaly],
|
||||
interval: '10',
|
||||
},
|
||||
]);
|
||||
mockUseInstalledSecurityJobNameById.mockReturnValue({
|
||||
loading: false,
|
||||
jobNameById: { [anomaly.jobId]: jobName },
|
||||
});
|
||||
|
||||
const { getByTestId } = render(<AnomaliesUserTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(getByTestId(`explorer-link-${anomaly.jobId}`).textContent).toContain(jobName);
|
||||
});
|
||||
describe('toggle query', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(hasMlUserPermissions as jest.Mock).mockReturnValue(true);
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle });
|
||||
mockUseAnomaliesTableData.mockReturnValue([
|
||||
false,
|
||||
{
|
||||
|
@ -45,42 +95,42 @@ describe('Anomalies user table', () => {
|
|||
});
|
||||
|
||||
test('toggleQuery updates toggleStatus', () => {
|
||||
const wrapper = mount(<AnomaliesUserTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
const { getByTestId } = render(<AnomaliesUserTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(false);
|
||||
wrapper.find('[data-test-subj="query-toggle-header"]').first().simulate('click');
|
||||
fireEvent.click(getByTestId('query-toggle-header'));
|
||||
expect(mockSetToggle).toBeCalledWith(false);
|
||||
expect(mockUseAnomaliesTableData.mock.calls[1][0].skip).toEqual(true);
|
||||
});
|
||||
|
||||
test('toggleStatus=true, do not skip', () => {
|
||||
mount(<AnomaliesUserTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
render(<AnomaliesUserTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(false);
|
||||
});
|
||||
|
||||
test('toggleStatus=true, render components', () => {
|
||||
const wrapper = mount(<AnomaliesUserTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
const { queryByTestId } = render(<AnomaliesUserTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(wrapper.find('[data-test-subj="user-anomalies-table"]').exists()).toBe(true);
|
||||
expect(queryByTestId('user-anomalies-table')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('toggleStatus=false, do not render components', () => {
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
|
||||
const wrapper = mount(<AnomaliesUserTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
const { queryByTestId } = render(<AnomaliesUserTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(wrapper.find('[data-test-subj="user-anomalies-table"]').exists()).toBe(false);
|
||||
expect(queryByTestId('user-anomalies-table')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('toggleStatus=false, skip', () => {
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle });
|
||||
mount(<AnomaliesUserTable {...testProps} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
render(<AnomaliesUserTable {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(mockUseAnomaliesTableData.mock.calls[0][0].skip).toEqual(true);
|
||||
|
|
|
@ -30,7 +30,7 @@ import { SelectInterval } from './select_interval';
|
|||
import { useDeepEqualSelector } from '../../../hooks/use_selector';
|
||||
import { usersActions, usersSelectors } from '../../../../explore/users/store';
|
||||
import type { State } from '../../../store/types';
|
||||
import { useInstalledSecurityJobsIds } from '../hooks/use_installed_security_jobs';
|
||||
import { useInstalledSecurityJobNameById } from '../hooks/use_installed_security_jobs';
|
||||
|
||||
const sorting = {
|
||||
sort: {
|
||||
|
@ -63,7 +63,8 @@ const AnomaliesUserTableComponent: React.FC<AnomaliesUserTableProps> = ({
|
|||
[setQuerySkip, setToggleStatus]
|
||||
);
|
||||
|
||||
const { jobIds, loading: loadingJobs } = useInstalledSecurityJobsIds();
|
||||
const { jobNameById, loading: loadingJobs } = useInstalledSecurityJobNameById();
|
||||
const jobIds = useMemo(() => Object.keys(jobNameById), [jobNameById]);
|
||||
|
||||
const getAnomaliesUserTableFilterQuerySelector = useMemo(
|
||||
() => usersSelectors.usersAnomaliesJobIdFilterSelector(),
|
||||
|
@ -119,8 +120,7 @@ const AnomaliesUserTableComponent: React.FC<AnomaliesUserTableProps> = ({
|
|||
aggregationInterval: selectedInterval,
|
||||
});
|
||||
|
||||
const users = convertAnomaliesToUsers(tableData, userName);
|
||||
|
||||
const users = convertAnomaliesToUsers(tableData, jobNameById, userName);
|
||||
const columns = getAnomaliesUserTableColumnsCurated(type, startDate, endDate);
|
||||
const pagination = {
|
||||
initialPageIndex: 0,
|
||||
|
@ -156,6 +156,7 @@ const AnomaliesUserTableComponent: React.FC<AnomaliesUserTableProps> = ({
|
|||
onSelect={onSelectJobId}
|
||||
selectedJobIds={selectedJobIds}
|
||||
jobIds={jobIds}
|
||||
jobNameById={jobNameById}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -18,7 +18,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
});
|
||||
|
||||
test('it returns expected anomalies from a host', () => {
|
||||
const entities = convertAnomaliesToHosts(anomalies);
|
||||
const entities = convertAnomaliesToHosts(anomalies, {});
|
||||
const expected: AnomaliesByHost[] = [
|
||||
{
|
||||
anomaly: {
|
||||
|
@ -61,6 +61,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
time: 1560664800000,
|
||||
},
|
||||
hostName: 'zeek-iowa',
|
||||
jobName: 'job-1',
|
||||
},
|
||||
{
|
||||
anomaly: {
|
||||
|
@ -103,13 +104,14 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
time: 1560664800000,
|
||||
},
|
||||
hostName: 'zeek-iowa',
|
||||
jobName: 'job-2',
|
||||
},
|
||||
];
|
||||
expect(entities).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it returns empty anomalies if sent in a null', () => {
|
||||
const entities = convertAnomaliesToHosts(null);
|
||||
const entities = convertAnomaliesToHosts(null, {});
|
||||
const expected: AnomaliesByHost[] = [];
|
||||
expect(entities).toEqual(expected);
|
||||
});
|
||||
|
@ -123,7 +125,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
{ 'user.name': 'root' },
|
||||
];
|
||||
|
||||
const entities = convertAnomaliesToHosts(anomalies, 'zeek-iowa');
|
||||
const entities = convertAnomaliesToHosts(anomalies, {}, 'zeek-iowa');
|
||||
const expected: AnomaliesByHost[] = [
|
||||
{
|
||||
anomaly: {
|
||||
|
@ -166,6 +168,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
time: 1560664800000,
|
||||
},
|
||||
hostName: 'zeek-iowa',
|
||||
jobName: 'job-2',
|
||||
},
|
||||
];
|
||||
expect(entities).toEqual(expected);
|
||||
|
@ -182,7 +185,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
|
||||
anomalies.anomalies[1].entityName = 'something-else';
|
||||
anomalies.anomalies[1].entityValue = 'something-else';
|
||||
const entities = convertAnomaliesToHosts(anomalies, 'zeek-iowa');
|
||||
const entities = convertAnomaliesToHosts(anomalies, {}, 'zeek-iowa');
|
||||
const expected: AnomaliesByHost[] = [
|
||||
{
|
||||
anomaly: {
|
||||
|
@ -225,13 +228,14 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
time: 1560664800000,
|
||||
},
|
||||
hostName: 'zeek-iowa',
|
||||
jobName: 'job-2',
|
||||
},
|
||||
];
|
||||
expect(entities).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it returns empty anomalies if sent in the name of one that does not exist', () => {
|
||||
const entities = convertAnomaliesToHosts(anomalies, 'some-made-up-name-here-for-you');
|
||||
const entities = convertAnomaliesToHosts(anomalies, {}, 'some-made-up-name-here-for-you');
|
||||
const expected: AnomaliesByHost[] = [];
|
||||
expect(entities).toEqual(expected);
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ import { getHostNameFromInfluencers } from '../influencers/get_host_name_from_in
|
|||
|
||||
export const convertAnomaliesToHosts = (
|
||||
anomalies: Anomalies | null,
|
||||
jobNameById: Record<string, string | undefined>,
|
||||
hostName?: string
|
||||
): AnomaliesByHost[] => {
|
||||
if (anomalies == null) {
|
||||
|
@ -17,11 +18,25 @@ export const convertAnomaliesToHosts = (
|
|||
} else {
|
||||
return anomalies.anomalies.reduce<AnomaliesByHost[]>((accum, item) => {
|
||||
if (getHostNameFromEntity(item, hostName)) {
|
||||
return [...accum, { hostName: item.entityValue, anomaly: item }];
|
||||
return [
|
||||
...accum,
|
||||
{
|
||||
hostName: item.entityValue,
|
||||
jobName: jobNameById[item.jobId] ?? item.jobId,
|
||||
anomaly: item,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
const hostNameFromInfluencers = getHostNameFromInfluencers(item.influencers, hostName);
|
||||
if (hostNameFromInfluencers != null) {
|
||||
return [...accum, { hostName: hostNameFromInfluencers, anomaly: item }];
|
||||
return [
|
||||
...accum,
|
||||
{
|
||||
hostName: hostNameFromInfluencers,
|
||||
jobName: jobNameById[item.jobId] ?? item.jobId,
|
||||
anomaly: item,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return accum;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
test('it returns expected anomalies from a network if is part of the entityName and is a source.ip', () => {
|
||||
anomalies.anomalies[0].entityName = 'source.ip';
|
||||
anomalies.anomalies[0].entityValue = '127.0.0.1';
|
||||
const entities = convertAnomaliesToNetwork(anomalies);
|
||||
const entities = convertAnomaliesToNetwork(anomalies, {});
|
||||
const expected: AnomaliesByNetwork[] = [
|
||||
{
|
||||
anomaly: {
|
||||
|
@ -64,6 +64,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
},
|
||||
ip: '127.0.0.1',
|
||||
type: 'source.ip',
|
||||
jobName: 'job-1',
|
||||
},
|
||||
];
|
||||
expect(entities).toEqual(expected);
|
||||
|
@ -72,7 +73,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
test('it returns expected anomalies from a network if is part of the entityName and is a destination.ip', () => {
|
||||
anomalies.anomalies[0].entityName = 'destination.ip';
|
||||
anomalies.anomalies[0].entityValue = '127.0.0.1';
|
||||
const entities = convertAnomaliesToNetwork(anomalies);
|
||||
const entities = convertAnomaliesToNetwork(anomalies, {});
|
||||
const expected: AnomaliesByNetwork[] = [
|
||||
{
|
||||
anomaly: {
|
||||
|
@ -116,6 +117,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
},
|
||||
ip: '127.0.0.1',
|
||||
type: 'destination.ip',
|
||||
jobName: 'job-1',
|
||||
},
|
||||
];
|
||||
expect(entities).toEqual(expected);
|
||||
|
@ -125,7 +127,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
anomalies.anomalies[0].entityName = 'not-an-ip';
|
||||
anomalies.anomalies[0].entityValue = 'not-an-ip';
|
||||
anomalies.anomalies[0].influencers = [{ 'source.ip': '127.0.0.1' }];
|
||||
const entities = convertAnomaliesToNetwork(anomalies);
|
||||
const entities = convertAnomaliesToNetwork(anomalies, {});
|
||||
const expected: AnomaliesByNetwork[] = [
|
||||
{
|
||||
anomaly: {
|
||||
|
@ -165,6 +167,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
},
|
||||
ip: '127.0.0.1',
|
||||
type: 'source.ip',
|
||||
jobName: 'job-1',
|
||||
},
|
||||
];
|
||||
expect(entities).toEqual(expected);
|
||||
|
@ -174,7 +177,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
anomalies.anomalies[0].entityName = 'not-an-ip';
|
||||
anomalies.anomalies[0].entityValue = 'not-an-ip';
|
||||
anomalies.anomalies[0].influencers = [{ 'destination.ip': '127.0.0.1' }];
|
||||
const entities = convertAnomaliesToNetwork(anomalies);
|
||||
const entities = convertAnomaliesToNetwork(anomalies, {});
|
||||
const expected: AnomaliesByNetwork[] = [
|
||||
{
|
||||
anomaly: {
|
||||
|
@ -214,13 +217,14 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
},
|
||||
ip: '127.0.0.1',
|
||||
type: 'destination.ip',
|
||||
jobName: 'job-1',
|
||||
},
|
||||
];
|
||||
expect(entities).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it returns empty anomalies if sent in a null', () => {
|
||||
const entities = convertAnomaliesToNetwork(null);
|
||||
const entities = convertAnomaliesToNetwork(null, {});
|
||||
const expected: AnomaliesByNetwork[] = [];
|
||||
expect(entities).toEqual(expected);
|
||||
});
|
||||
|
@ -233,7 +237,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
{ 'process.name': 'du' },
|
||||
{ 'user.name': 'root' },
|
||||
];
|
||||
const entities = convertAnomaliesToNetwork(anomalies, '127.0.0.1');
|
||||
const entities = convertAnomaliesToNetwork(anomalies, {}, '127.0.0.1');
|
||||
const expected: AnomaliesByNetwork[] = [
|
||||
{
|
||||
anomaly: {
|
||||
|
@ -277,6 +281,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
},
|
||||
ip: '127.0.0.1',
|
||||
type: 'source.ip',
|
||||
jobName: 'job-1',
|
||||
},
|
||||
];
|
||||
expect(entities).toEqual(expected);
|
||||
|
@ -290,7 +295,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
{ 'process.name': 'du' },
|
||||
{ 'user.name': 'root' },
|
||||
];
|
||||
const entities = convertAnomaliesToNetwork(anomalies, '127.0.0.1');
|
||||
const entities = convertAnomaliesToNetwork(anomalies, {}, '127.0.0.1');
|
||||
const expected: AnomaliesByNetwork[] = [
|
||||
{
|
||||
anomaly: {
|
||||
|
@ -334,6 +339,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
},
|
||||
ip: '127.0.0.1',
|
||||
type: 'destination.ip',
|
||||
jobName: 'job-1',
|
||||
},
|
||||
];
|
||||
expect(entities).toEqual(expected);
|
||||
|
@ -347,7 +353,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
{ 'process.name': 'du' },
|
||||
{ 'user.name': 'root' },
|
||||
];
|
||||
const entities = convertAnomaliesToNetwork(anomalies, '127.0.0.1');
|
||||
const entities = convertAnomaliesToNetwork(anomalies, {}, '127.0.0.1');
|
||||
const expected: AnomaliesByNetwork[] = [
|
||||
{
|
||||
anomaly: {
|
||||
|
@ -391,6 +397,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
},
|
||||
ip: '127.0.0.1',
|
||||
type: 'source.ip',
|
||||
jobName: 'job-1',
|
||||
},
|
||||
];
|
||||
expect(entities).toEqual(expected);
|
||||
|
@ -404,7 +411,7 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
{ 'process.name': 'du' },
|
||||
{ 'user.name': 'root' },
|
||||
];
|
||||
const entities = convertAnomaliesToNetwork(anomalies, '127.0.0.1');
|
||||
const entities = convertAnomaliesToNetwork(anomalies, {}, '127.0.0.1');
|
||||
const expected: AnomaliesByNetwork[] = [
|
||||
{
|
||||
anomaly: {
|
||||
|
@ -448,13 +455,14 @@ describe('convert_anomalies_to_hosts', () => {
|
|||
},
|
||||
ip: '127.0.0.1',
|
||||
type: 'destination.ip',
|
||||
jobName: 'job-1',
|
||||
},
|
||||
];
|
||||
expect(entities).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it returns empty anomalies if sent in the name of one that does not exist', () => {
|
||||
const entities = convertAnomaliesToNetwork(anomalies, 'some-made-up-name-here-for-you');
|
||||
const entities = convertAnomaliesToNetwork(anomalies, {}, 'some-made-up-name-here-for-you');
|
||||
const expected: AnomaliesByNetwork[] = [];
|
||||
expect(entities).toEqual(expected);
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ import { getNetworkFromInfluencers } from '../influencers/get_network_from_influ
|
|||
|
||||
export const convertAnomaliesToNetwork = (
|
||||
anomalies: Anomalies | null,
|
||||
jobNameById: Record<string, string | undefined>,
|
||||
ip?: string
|
||||
): AnomaliesByNetwork[] => {
|
||||
if (anomalies == null) {
|
||||
|
@ -18,11 +19,27 @@ export const convertAnomaliesToNetwork = (
|
|||
} else {
|
||||
return anomalies.anomalies.reduce<AnomaliesByNetwork[]>((accum, item) => {
|
||||
if (isDestinationOrSource(item.entityName) && getNetworkFromEntity(item, ip)) {
|
||||
return [...accum, { ip: item.entityValue, type: item.entityName, anomaly: item }];
|
||||
return [
|
||||
...accum,
|
||||
{
|
||||
ip: item.entityValue,
|
||||
type: item.entityName,
|
||||
jobName: jobNameById[item.jobId] ?? item.jobId,
|
||||
anomaly: item,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
const network = getNetworkFromInfluencers(item.influencers, ip);
|
||||
if (network != null) {
|
||||
return [...accum, { ip: network.ip, type: network.type, anomaly: item }];
|
||||
return [
|
||||
...accum,
|
||||
{
|
||||
ip: network.ip,
|
||||
type: network.type,
|
||||
jobName: jobNameById[item.jobId] ?? item.jobId,
|
||||
anomaly: item,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return accum;
|
||||
}
|
||||
|
|
|
@ -11,23 +11,25 @@ import type { AnomaliesByUser } from '../types';
|
|||
|
||||
describe('convert_anomalies_to_users', () => {
|
||||
test('it returns expected anomalies from a user', () => {
|
||||
const entities = convertAnomaliesToUsers(mockAnomalies);
|
||||
const entities = convertAnomaliesToUsers(mockAnomalies, {});
|
||||
|
||||
const expected: AnomaliesByUser[] = [
|
||||
{
|
||||
anomaly: mockAnomalies.anomalies[0],
|
||||
userName: 'root',
|
||||
jobName: 'job-1',
|
||||
},
|
||||
{
|
||||
anomaly: mockAnomalies.anomalies[1],
|
||||
userName: 'root',
|
||||
jobName: 'job-2',
|
||||
},
|
||||
];
|
||||
expect(entities).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it returns empty anomalies if sent in a null', () => {
|
||||
const entities = convertAnomaliesToUsers(null);
|
||||
const entities = convertAnomaliesToUsers(null, {});
|
||||
const expected: AnomaliesByUser[] = [];
|
||||
expect(entities).toEqual(expected);
|
||||
});
|
||||
|
@ -50,11 +52,12 @@ describe('convert_anomalies_to_users', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const entities = convertAnomaliesToUsers(anomalies, 'root');
|
||||
const entities = convertAnomaliesToUsers(anomalies, {}, 'root');
|
||||
const expected: AnomaliesByUser[] = [
|
||||
{
|
||||
anomaly: anomalies.anomalies[1],
|
||||
userName: 'root',
|
||||
jobName: 'job-2',
|
||||
},
|
||||
];
|
||||
expect(entities).toEqual(expected);
|
||||
|
@ -82,18 +85,19 @@ describe('convert_anomalies_to_users', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const entities = convertAnomaliesToUsers(anomalies, 'root');
|
||||
const entities = convertAnomaliesToUsers(anomalies, {}, 'root');
|
||||
const expected: AnomaliesByUser[] = [
|
||||
{
|
||||
anomaly: anomalies.anomalies[1],
|
||||
userName: 'root',
|
||||
jobName: 'job-2',
|
||||
},
|
||||
];
|
||||
expect(entities).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it returns empty anomalies if sent in the name of one that does not exist', () => {
|
||||
const entities = convertAnomaliesToUsers(mockAnomalies, 'some-made-up-name-here-for-you');
|
||||
const entities = convertAnomaliesToUsers(mockAnomalies, {}, 'some-made-up-name-here-for-you');
|
||||
const expected: AnomaliesByUser[] = [];
|
||||
expect(entities).toEqual(expected);
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ import { getUserNameFromInfluencers } from '../influencers/get_user_name_from_in
|
|||
|
||||
export const convertAnomaliesToUsers = (
|
||||
anomalies: Anomalies | null,
|
||||
jobNameById: Record<string, string | undefined>,
|
||||
userName?: string
|
||||
): AnomaliesByUser[] => {
|
||||
if (anomalies == null) {
|
||||
|
@ -17,11 +18,25 @@ export const convertAnomaliesToUsers = (
|
|||
} else {
|
||||
return anomalies.anomalies.reduce<AnomaliesByUser[]>((accum, item) => {
|
||||
if (getUserNameFromEntity(item, userName)) {
|
||||
return [...accum, { userName: item.entityValue, anomaly: item }];
|
||||
return [
|
||||
...accum,
|
||||
{
|
||||
userName: item.entityValue,
|
||||
jobName: jobNameById[item.jobId] ?? item.jobId,
|
||||
anomaly: item,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
const userNameFromInfluencers = getUserNameFromInfluencers(item.influencers, userName);
|
||||
if (userNameFromInfluencers != null) {
|
||||
return [...accum, { userName: userNameFromInfluencers, anomaly: item }];
|
||||
return [
|
||||
...accum,
|
||||
{
|
||||
userName: userNameFromInfluencers,
|
||||
jobName: jobNameById[item.jobId] ?? item.jobId,
|
||||
anomaly: item,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return accum;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ describe('getAnomaliesDefaultTableColumns', () => {
|
|||
AnomaliesBy
|
||||
>;
|
||||
const anomaly: AnomaliesBy = {
|
||||
jobName: undefined,
|
||||
anomaly: {
|
||||
detectorIndex: 0,
|
||||
entityName: 'entity-name-1',
|
||||
|
|
|
@ -32,14 +32,14 @@ export const getAnomaliesDefaultTableColumns = (
|
|||
] => [
|
||||
{
|
||||
name: i18n.DETECTOR,
|
||||
field: 'anomaly.jobId',
|
||||
field: 'jobName',
|
||||
sortable: true,
|
||||
render: (jobId, anomalyBy) => (
|
||||
render: (jobName, anomalyBy) => (
|
||||
<ExplorerLink
|
||||
score={anomalyBy.anomaly}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
linkName={jobId}
|
||||
linkName={jobName}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
|
|
@ -20,13 +20,20 @@ const withTheme = (storyFn: () => ReactNode) => (
|
|||
storiesOf('JobIdFilter', module)
|
||||
.addDecorator(withTheme)
|
||||
.add('empty', () => (
|
||||
<JobIdFilter title="Job id" selectedJobIds={[]} jobIds={[]} onSelect={action('onSelect')} />
|
||||
<JobIdFilter
|
||||
title="Job id"
|
||||
selectedJobIds={[]}
|
||||
jobIds={[]}
|
||||
jobNameById={{}}
|
||||
onSelect={action('onSelect')}
|
||||
/>
|
||||
))
|
||||
.add('one selected item', () => (
|
||||
<JobIdFilter
|
||||
title="Job id"
|
||||
selectedJobIds={['test_job_1']}
|
||||
jobIds={['test_job_1', 'test_job_2', 'test_job_3', 'test_job_4']}
|
||||
jobNameById={{}}
|
||||
onSelect={action('onSelect')}
|
||||
/>
|
||||
))
|
||||
|
@ -35,6 +42,7 @@ storiesOf('JobIdFilter', module)
|
|||
title="Job id"
|
||||
selectedJobIds={['test_job_2', 'test_job_3']}
|
||||
jobIds={['test_job_1', 'test_job_2', 'test_job_3', 'test_job_4']}
|
||||
jobNameById={{}}
|
||||
onSelect={action('onSelect')}
|
||||
/>
|
||||
))
|
||||
|
@ -43,6 +51,7 @@ storiesOf('JobIdFilter', module)
|
|||
title="Job id"
|
||||
selectedJobIds={[]}
|
||||
jobIds={['test_job_1', 'test_job_2', 'test_job_3', 'test_job_4']}
|
||||
jobNameById={{}}
|
||||
onSelect={action('onSelect')}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -9,10 +9,18 @@ import { fireEvent, render } from '@testing-library/react';
|
|||
import React from 'react';
|
||||
import { JobIdFilter } from './job_id_filter';
|
||||
|
||||
const JOB_IDS = ['test_job_1', 'test_job_2', 'test_job_3', 'test_job_4'];
|
||||
|
||||
describe('JobIdFilter', () => {
|
||||
it('is disabled when job id is empty', () => {
|
||||
const { getByTestId } = render(
|
||||
<JobIdFilter title="Job id" selectedJobIds={[]} jobIds={[]} onSelect={jest.fn()} />
|
||||
<JobIdFilter
|
||||
title="Job id"
|
||||
selectedJobIds={[]}
|
||||
jobIds={[]}
|
||||
onSelect={jest.fn()}
|
||||
jobNameById={{}}
|
||||
/>
|
||||
);
|
||||
expect(getByTestId('job-id-filter-button')).toBeDisabled();
|
||||
});
|
||||
|
@ -23,8 +31,9 @@ describe('JobIdFilter', () => {
|
|||
<JobIdFilter
|
||||
title="Job id"
|
||||
selectedJobIds={[]}
|
||||
jobIds={['test_job_1', 'test_job_2', 'test_job_3', 'test_job_4']}
|
||||
jobIds={JOB_IDS}
|
||||
onSelect={onSelectCb}
|
||||
jobNameById={{}}
|
||||
/>
|
||||
);
|
||||
fireEvent.click(getByTestId('job-id-filter-button'));
|
||||
|
@ -38,8 +47,9 @@ describe('JobIdFilter', () => {
|
|||
<JobIdFilter
|
||||
title="Job id"
|
||||
selectedJobIds={['test_job_2']}
|
||||
jobIds={['test_job_1', 'test_job_2', 'test_job_3', 'test_job_4']}
|
||||
jobIds={JOB_IDS}
|
||||
onSelect={jest.fn()}
|
||||
jobNameById={{}}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -49,4 +59,21 @@ describe('JobIdFilter', () => {
|
|||
getByTestId('job-id-filter-item-test_job_2').querySelector('span[data-euiicon-type=check]')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays job name when it is available', () => {
|
||||
const jobName = 'TEST_JOB_NAME';
|
||||
const { getByTestId } = render(
|
||||
<JobIdFilter
|
||||
title="Job id"
|
||||
selectedJobIds={[]}
|
||||
jobIds={['test_job']}
|
||||
onSelect={jest.fn()}
|
||||
jobNameById={{ test_job: jobName }}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId('job-id-filter-button'));
|
||||
|
||||
expect(getByTestId('job-id-filter-item-test_job').textContent).toBe(jobName);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,9 +10,10 @@ import { EuiFilterButton, EuiFilterGroup, EuiFilterSelectItem, EuiPopover } from
|
|||
export const JobIdFilter: React.FC<{
|
||||
selectedJobIds: string[];
|
||||
jobIds: string[];
|
||||
jobNameById: Record<string, string | undefined>;
|
||||
onSelect: (jobIds: string[]) => void;
|
||||
title: string;
|
||||
}> = ({ selectedJobIds, onSelect, title, jobIds }) => {
|
||||
}> = ({ selectedJobIds, onSelect, title, jobIds, jobNameById }) => {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
|
||||
const onButtonClick = useCallback(() => {
|
||||
|
@ -50,7 +51,7 @@ export const JobIdFilter: React.FC<{
|
|||
{title}
|
||||
</EuiFilterButton>
|
||||
),
|
||||
[isPopoverOpen, onButtonClick, title, selectedJobIds.length, jobIds]
|
||||
[jobIds.length, selectedJobIds.length, isPopoverOpen, onButtonClick, title]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -69,7 +70,7 @@ export const JobIdFilter: React.FC<{
|
|||
key={id}
|
||||
onClick={() => updateSelection(id)}
|
||||
>
|
||||
{id}
|
||||
{jobNameById[id] ?? id}
|
||||
</EuiFilterSelectItem>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -65,6 +65,7 @@ export type NarrowDateRange = (score: Anomaly, interval: string) => void;
|
|||
|
||||
export interface AnomaliesBy {
|
||||
anomaly: Anomaly;
|
||||
jobName: string | undefined;
|
||||
}
|
||||
|
||||
export interface AnomaliesByHost extends AnomaliesBy {
|
||||
|
|
|
@ -45,12 +45,18 @@ export const filterJobs = ({
|
|||
* @param jobs to filter
|
||||
* @param filterQuery user-provided search string to filter for occurrence in job names/description
|
||||
*/
|
||||
export const searchFilter = (jobs: SecurityJob[], filterQuery?: string): SecurityJob[] =>
|
||||
jobs.filter((job) =>
|
||||
filterQuery == null
|
||||
export const searchFilter = (jobs: SecurityJob[], filterQuery?: string): SecurityJob[] => {
|
||||
const lowerCaseFilterQuery = filterQuery?.toLowerCase();
|
||||
return jobs.filter((job) =>
|
||||
lowerCaseFilterQuery == null
|
||||
? true
|
||||
: job.id.includes(filterQuery) || job.description.includes(filterQuery)
|
||||
: job.id.includes(lowerCaseFilterQuery) ||
|
||||
job.customSettings?.security_app_display_name
|
||||
?.toLowerCase()
|
||||
?.includes(lowerCaseFilterQuery) ||
|
||||
job.description.toLowerCase().includes(lowerCaseFilterQuery)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Given an array of titles this will always return the same string for usage within
|
||||
|
|
|
@ -49,6 +49,31 @@ describe('useSecurityJobsHelpers', () => {
|
|||
memory_status: '',
|
||||
moduleId: 'security_linux_v3',
|
||||
processed_record_count: 0,
|
||||
customSettings: {
|
||||
created_by: 'ml-module-siem-auditbeat',
|
||||
custom_urls: [
|
||||
{
|
||||
url_name: 'Host Details by process name',
|
||||
url_value:
|
||||
"siem#/ml-hosts/$host.name$?kqlQuery=(filterQuery:(expression:'process.name%20:%20%22$process.name$%22',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))",
|
||||
},
|
||||
{
|
||||
url_name: 'Host Details by user name',
|
||||
url_value:
|
||||
"siem#/ml-hosts/$host.name$?kqlQuery=(filterQuery:(expression:'user.name%20:%20%22$user.name$%22',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))",
|
||||
},
|
||||
{
|
||||
url_name: 'Hosts Overview by process name',
|
||||
url_value:
|
||||
"siem#/ml-hosts?kqlQuery=(filterQuery:(expression:'process.name%20:%20%22$process.name$%22',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))",
|
||||
},
|
||||
{
|
||||
url_name: 'Hosts Overview by user name',
|
||||
url_value:
|
||||
"siem#/ml-hosts?kqlQuery=(filterQuery:(expression:'user.name%20:%20%22$user.name$%22',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ export const moduleToSecurityJob = (
|
|||
awaitingNodeAssignment: false,
|
||||
jobTags: {},
|
||||
bucketSpanSeconds: 900,
|
||||
customSettings: moduleJob.config.custom_settings,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -75,8 +75,12 @@ const getJobsTableColumns = (
|
|||
) => [
|
||||
{
|
||||
name: i18n.COLUMN_JOB_NAME,
|
||||
render: ({ id, description }: SecurityJob) => (
|
||||
<JobName id={id} description={description} basePath={basePath} />
|
||||
render: ({ id, description, customSettings }: SecurityJob) => (
|
||||
<JobName
|
||||
id={customSettings?.security_app_display_name ?? id}
|
||||
description={description}
|
||||
basePath={basePath}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
|
@ -74,6 +74,7 @@ export interface ModuleJob {
|
|||
custom_settings: {
|
||||
created_by: string;
|
||||
custom_urls: CustomURL[];
|
||||
security_app_display_name?: string;
|
||||
};
|
||||
job_type: string;
|
||||
};
|
||||
|
@ -121,6 +122,9 @@ export interface SecurityJob extends MlSummaryJob {
|
|||
isCompatible: boolean;
|
||||
isInstalled: boolean;
|
||||
isElasticJob: boolean;
|
||||
customSettings?: {
|
||||
security_app_display_name?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AugmentedSecurityJobFields {
|
||||
|
|
|
@ -204,7 +204,7 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta
|
|||
endDate={to}
|
||||
skip={isInitializing}
|
||||
>
|
||||
{({ isLoadingAnomaliesData, anomaliesData }) => (
|
||||
{({ isLoadingAnomaliesData, anomaliesData, jobNameById }) => (
|
||||
<HostOverviewManage
|
||||
id={id}
|
||||
isInDetailsSidePanel={false}
|
||||
|
@ -220,6 +220,7 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta
|
|||
inspect={inspect}
|
||||
hostName={detailName}
|
||||
indexNames={selectedPatterns}
|
||||
jobNameById={jobNameById}
|
||||
/>
|
||||
)}
|
||||
</AnomalyTableProvider>
|
||||
|
|
|
@ -144,6 +144,7 @@ exports[`IP Overview Component rendering it renders the default IP Overview 1`]
|
|||
ip="10.10.10.10"
|
||||
isInDetailsSidePanel={false}
|
||||
isLoadingAnomaliesData={false}
|
||||
jobNameById={Object {}}
|
||||
loading={false}
|
||||
narrowDateRange={[MockFunction]}
|
||||
startDate="2019-06-15T06:00:00.000Z"
|
||||
|
@ -296,6 +297,7 @@ exports[`IP Overview Component rendering it renders the side panel IP overview 1
|
|||
ip="10.10.10.10"
|
||||
isInDetailsSidePanel={true}
|
||||
isLoadingAnomaliesData={false}
|
||||
jobNameById={Object {}}
|
||||
loading={false}
|
||||
narrowDateRange={[MockFunction]}
|
||||
startDate="2019-06-15T06:00:00.000Z"
|
||||
|
|
|
@ -55,6 +55,7 @@ describe('IP Overview Component', () => {
|
|||
flowTarget: FlowTargetSourceDest;
|
||||
}>,
|
||||
indexPatterns: [],
|
||||
jobNameById: {},
|
||||
};
|
||||
|
||||
test('it renders the default IP Overview', () => {
|
||||
|
|
|
@ -56,6 +56,7 @@ export interface IpOverviewProps {
|
|||
startDate: string;
|
||||
type: networkModel.NetworkType;
|
||||
indexPatterns: string[];
|
||||
jobNameById: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
export const IpOverview = React.memo<IpOverviewProps>(
|
||||
|
@ -74,6 +75,7 @@ export const IpOverview = React.memo<IpOverviewProps>(
|
|||
anomaliesData,
|
||||
narrowDateRange,
|
||||
indexPatterns,
|
||||
jobNameById,
|
||||
}) => {
|
||||
const capabilities = useMlCapabilities();
|
||||
const userPermissions = hasMlUserPermissions(capabilities);
|
||||
|
@ -109,6 +111,7 @@ export const IpOverview = React.memo<IpOverviewProps>(
|
|||
endDate={endDate}
|
||||
isLoading={isLoadingAnomaliesData}
|
||||
narrowDateRange={narrowDateRange}
|
||||
jobNameById={jobNameById}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
|
|
@ -51,7 +51,7 @@ import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml
|
|||
import { useAlertsPrivileges } from '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges';
|
||||
import { navTabsNetworkDetails } from './nav_tabs';
|
||||
import { NetworkDetailsTabs } from './details_tabs';
|
||||
import { useInstalledSecurityJobsIds } from '../../../../common/components/ml/hooks/use_installed_security_jobs';
|
||||
import { useInstalledSecurityJobNameById } from '../../../../common/components/ml/hooks/use_installed_security_jobs';
|
||||
|
||||
export { getTrailingBreadcrumbs } from './utils';
|
||||
|
||||
|
@ -137,7 +137,8 @@ const NetworkDetailsComponent: React.FC = () => {
|
|||
ip,
|
||||
});
|
||||
|
||||
const { jobIds } = useInstalledSecurityJobsIds();
|
||||
const { jobNameById } = useInstalledSecurityJobNameById();
|
||||
const jobIds = useMemo(() => Object.keys(jobNameById), [jobNameById]);
|
||||
const [isLoadingAnomaliesData, anomaliesData] = useAnomaliesTableData({
|
||||
criteriaFields: networkToCriteria(detailName, flowTarget),
|
||||
startDate: from,
|
||||
|
@ -202,6 +203,7 @@ const NetworkDetailsComponent: React.FC = () => {
|
|||
endDate={to}
|
||||
narrowDateRange={narrowDateRange}
|
||||
indexPatterns={selectedPatterns}
|
||||
jobNameById={jobNameById}
|
||||
/>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
|
|
|
@ -197,7 +197,7 @@ const UsersDetailsComponent: React.FC<UsersDetailsProps> = ({
|
|||
endDate={to}
|
||||
skip={isInitializing}
|
||||
>
|
||||
{({ isLoadingAnomaliesData, anomaliesData }) => (
|
||||
{({ isLoadingAnomaliesData, anomaliesData, jobNameById }) => (
|
||||
<UserOverview
|
||||
userName={detailName}
|
||||
id={QUERY_ID}
|
||||
|
@ -210,6 +210,7 @@ const UsersDetailsComponent: React.FC<UsersDetailsProps> = ({
|
|||
endDate={to}
|
||||
narrowDateRange={narrowDateRange}
|
||||
indexPatterns={selectedPatterns}
|
||||
jobNameById={jobNameById}
|
||||
/>
|
||||
)}
|
||||
</AnomalyTableProvider>
|
||||
|
|
|
@ -45,11 +45,11 @@ export const useAnomaliesColumns = (
|
|||
truncateText: true,
|
||||
mobileOptions: { show: true },
|
||||
'data-test-subj': 'anomalies-table-column-name',
|
||||
render: (name, { count, job }) => {
|
||||
render: (jobName, { count, job }) => {
|
||||
if (count > 0 || (job && isJobStarted(job.jobState, job.datafeedState))) {
|
||||
return name;
|
||||
return jobName;
|
||||
} else {
|
||||
return <MediumShadeText>{name}</MediumShadeText>;
|
||||
return <MediumShadeText>{jobName}</MediumShadeText>;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -198,6 +198,7 @@ exports[`Host Summary Component it renders the default Host Summary 1`] = `
|
|||
indexNames={Array []}
|
||||
isInDetailsSidePanel={false}
|
||||
isLoadingAnomaliesData={false}
|
||||
jobNameById={Object {}}
|
||||
loading={false}
|
||||
narrowDateRange={[MockFunction]}
|
||||
startDate="2019-06-15T06:00:00.000Z"
|
||||
|
@ -402,6 +403,7 @@ exports[`Host Summary Component it renders the panel view Host Summary 1`] = `
|
|||
indexNames={Array []}
|
||||
isInDetailsSidePanel={true}
|
||||
isLoadingAnomaliesData={false}
|
||||
jobNameById={Object {}}
|
||||
loading={false}
|
||||
narrowDateRange={[MockFunction]}
|
||||
startDate="2019-06-15T06:00:00.000Z"
|
||||
|
|
|
@ -42,6 +42,7 @@ describe('Host Summary Component', () => {
|
|||
narrowDateRange: jest.fn(),
|
||||
startDate: '2019-06-15T06:00:00.000Z',
|
||||
hostName: 'testHostName',
|
||||
jobNameById: {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -54,6 +54,7 @@ interface HostSummaryProps {
|
|||
endDate: string;
|
||||
narrowDateRange: NarrowDateRange;
|
||||
hostName: string;
|
||||
jobNameById: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
const HostRiskOverviewWrapper = styled(EuiFlexGroup)`
|
||||
|
@ -76,6 +77,7 @@ export const HostOverview = React.memo<HostSummaryProps>(
|
|||
narrowDateRange,
|
||||
startDate,
|
||||
hostName,
|
||||
jobNameById,
|
||||
}) => {
|
||||
const capabilities = useMlCapabilities();
|
||||
const userPermissions = hasMlUserPermissions(capabilities);
|
||||
|
@ -198,6 +200,7 @@ export const HostOverview = React.memo<HostSummaryProps>(
|
|||
endDate={endDate}
|
||||
isLoading={isLoadingAnomaliesData}
|
||||
narrowDateRange={narrowDateRange}
|
||||
jobNameById={jobNameById}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
@ -211,6 +214,7 @@ export const HostOverview = React.memo<HostSummaryProps>(
|
|||
narrowDateRange,
|
||||
startDate,
|
||||
userPermissions,
|
||||
jobNameById,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -171,6 +171,7 @@ exports[`User Summary Component it renders the default User Summary 1`] = `
|
|||
indexPatterns={Array []}
|
||||
isInDetailsSidePanel={false}
|
||||
isLoadingAnomaliesData={false}
|
||||
jobNameById={Object {}}
|
||||
loading={false}
|
||||
narrowDateRange={[MockFunction]}
|
||||
startDate="2019-06-15T06:00:00.000Z"
|
||||
|
@ -349,6 +350,7 @@ exports[`User Summary Component it renders the panel view User Summary 1`] = `
|
|||
indexPatterns={Array []}
|
||||
isInDetailsSidePanel={true}
|
||||
isLoadingAnomaliesData={false}
|
||||
jobNameById={Object {}}
|
||||
loading={false}
|
||||
narrowDateRange={[MockFunction]}
|
||||
startDate="2019-06-15T06:00:00.000Z"
|
||||
|
|
|
@ -55,6 +55,7 @@ describe('User Summary Component', () => {
|
|||
startDate: '2019-06-15T06:00:00.000Z',
|
||||
userName: 'testUserName',
|
||||
indexPatterns: [],
|
||||
jobNameById: {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -52,6 +52,7 @@ export interface UserSummaryProps {
|
|||
narrowDateRange: NarrowDateRange;
|
||||
userName: string;
|
||||
indexPatterns: string[];
|
||||
jobNameById: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
const UserRiskOverviewWrapper = styled(EuiFlexGroup)`
|
||||
|
@ -74,6 +75,7 @@ export const UserOverview = React.memo<UserSummaryProps>(
|
|||
endDate,
|
||||
userName,
|
||||
indexPatterns,
|
||||
jobNameById,
|
||||
}) => {
|
||||
const capabilities = useMlCapabilities();
|
||||
const userPermissions = hasMlUserPermissions(capabilities);
|
||||
|
@ -179,6 +181,7 @@ export const UserOverview = React.memo<UserSummaryProps>(
|
|||
endDate={endDate}
|
||||
isLoading={isLoadingAnomaliesData}
|
||||
narrowDateRange={narrowDateRange}
|
||||
jobNameById={jobNameById}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
@ -192,6 +195,7 @@ export const UserOverview = React.memo<UserSummaryProps>(
|
|||
narrowDateRange,
|
||||
startDate,
|
||||
userPermissions,
|
||||
jobNameById,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -33,8 +33,9 @@ jest.mock('../../../../common/components/ml/anomaly/anomaly_table_provider', ()
|
|||
children: (args: {
|
||||
anomaliesData: Anomalies;
|
||||
isLoadingAnomaliesData: boolean;
|
||||
jobNameById: Record<string, string | undefined>;
|
||||
}) => React.ReactNode;
|
||||
}) => children({ anomaliesData: mockAnomalies, isLoadingAnomaliesData: false }),
|
||||
}) => children({ anomaliesData: mockAnomalies, isLoadingAnomaliesData: false, jobNameById: {} }),
|
||||
}));
|
||||
|
||||
describe('Expandable Host Component', () => {
|
||||
|
|
|
@ -95,7 +95,7 @@ export const ExpandableHostDetails = ({
|
|||
endDate={to}
|
||||
skip={isInitializing}
|
||||
>
|
||||
{({ isLoadingAnomaliesData, anomaliesData }) => (
|
||||
{({ isLoadingAnomaliesData, anomaliesData, jobNameById }) => (
|
||||
<HostOverview
|
||||
contextID={contextID}
|
||||
id={ID}
|
||||
|
@ -110,6 +110,7 @@ export const ExpandableHostDetails = ({
|
|||
endDate={to}
|
||||
narrowDateRange={narrowDateRange}
|
||||
hostName={hostName}
|
||||
jobNameById={jobNameById}
|
||||
/>
|
||||
)}
|
||||
</AnomalyTableProvider>
|
||||
|
|
|
@ -29,7 +29,7 @@ import { useNetworkDetails } from '../../../../explore/network/containers/detail
|
|||
import { networkModel } from '../../../../explore/network/store';
|
||||
import { useAnomaliesTableData } from '../../../../common/components/ml/anomaly/use_anomalies_table_data';
|
||||
import { LandingCards } from '../../../../common/components/landing_cards';
|
||||
import { useInstalledSecurityJobsIds } from '../../../../common/components/ml/hooks/use_installed_security_jobs';
|
||||
import { useInstalledSecurityJobNameById } from '../../../../common/components/ml/hooks/use_installed_security_jobs';
|
||||
|
||||
interface ExpandableNetworkProps {
|
||||
expandedNetwork: { ip: string; flowTarget: FlowTargetSourceDest };
|
||||
|
@ -116,7 +116,8 @@ export const ExpandableNetworkDetails = ({
|
|||
});
|
||||
|
||||
useInvalidFilterQuery({ id, filterQuery, kqlError, query, startDate: from, endDate: to });
|
||||
const { jobIds } = useInstalledSecurityJobsIds();
|
||||
const { jobNameById } = useInstalledSecurityJobNameById();
|
||||
const jobIds = useMemo(() => Object.keys(jobNameById), [jobNameById]);
|
||||
const [isLoadingAnomaliesData, anomaliesData] = useAnomaliesTableData({
|
||||
criteriaFields: networkToCriteria(ip, flowTarget),
|
||||
startDate: from,
|
||||
|
@ -143,6 +144,7 @@ export const ExpandableNetworkDetails = ({
|
|||
endDate={to}
|
||||
narrowDateRange={narrowDateRange}
|
||||
indexPatterns={selectedPatterns}
|
||||
jobNameById={jobNameById}
|
||||
/>
|
||||
) : (
|
||||
<LandingCards />
|
||||
|
|
|
@ -33,8 +33,9 @@ jest.mock('../../../../common/components/ml/anomaly/anomaly_table_provider', ()
|
|||
children: (args: {
|
||||
anomaliesData: Anomalies;
|
||||
isLoadingAnomaliesData: boolean;
|
||||
jobNameById: Record<string, string | undefined>;
|
||||
}) => React.ReactNode;
|
||||
}) => children({ anomaliesData: mockAnomalies, isLoadingAnomaliesData: false }),
|
||||
}) => children({ anomaliesData: mockAnomalies, isLoadingAnomaliesData: false, jobNameById: {} }),
|
||||
}));
|
||||
|
||||
describe('Expandable Host Component', () => {
|
||||
|
|
|
@ -90,7 +90,7 @@ export const ExpandableUserDetails = ({
|
|||
endDate={to}
|
||||
skip={isInitializing}
|
||||
>
|
||||
{({ isLoadingAnomaliesData, anomaliesData }) => (
|
||||
{({ isLoadingAnomaliesData, anomaliesData, jobNameById }) => (
|
||||
<UserOverview
|
||||
userName={userName}
|
||||
isInDetailsSidePanel={true}
|
||||
|
@ -105,6 +105,7 @@ export const ExpandableUserDetails = ({
|
|||
endDate={to}
|
||||
narrowDateRange={narrowDateRange}
|
||||
indexPatterns={selectedPatterns}
|
||||
jobNameById={jobNameById}
|
||||
/>
|
||||
)}
|
||||
</AnomalyTableProvider>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue