Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add anomalies tab to user page #126079

Merged
merged 3 commits into from
Mar 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { useState, useEffect, useMemo } from 'react';

import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { DEFAULT_ANOMALY_SCORE } from '../../../../../common/constants';
import { anomaliesTableData } from '../api/anomalies_table_data';
import { InfluencerInput, Anomalies, CriteriaFields } from '../types';
Expand All @@ -23,6 +23,7 @@ interface Args {
threshold?: number;
skip?: boolean;
criteriaFields?: CriteriaFields[];
filterQuery?: estypes.QueryDslQueryContainer;
}

type Return = [boolean, Anomalies | null];
Expand Down Expand Up @@ -55,6 +56,7 @@ export const useAnomaliesTableData = ({
endDate,
threshold = -1,
skip = false,
filterQuery,
}: Args): Return => {
const [tableData, setTableData] = useState<Anomalies | null>(null);
const { isMlUser, jobs } = useInstalledSecurityJobs();
Expand Down Expand Up @@ -84,6 +86,7 @@ export const useAnomaliesTableData = ({
{
jobIds,
criteriaFields: criteriaFieldsInput,
influencersFilterQuery: filterQuery,
aggregationInterval: 'auto',
threshold: getThreshold(anomalyScore, threshold),
earliestMs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { Anomalies, InfluencerInput, CriteriaFields } from '../types';
import { KibanaServices } from '../../../lib/kibana';

Expand All @@ -19,6 +20,7 @@ export interface Body {
dateFormatTz: string;
maxRecords: number;
maxExamples: number;
influencersFilterQuery?: estypes.QueryDslQueryContainer;
}

export const anomaliesTableData = async (body: Body, signal: AbortSignal): Promise<Anomalies> => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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 { UsersType } from '../../../../users/store/model';
import { getCriteriaFromUsersType } from './get_criteria_from_users_type';

describe('get_criteria_from_user_type', () => {
test('returns user name from criteria if the user type is details', () => {
const criteria = getCriteriaFromUsersType(UsersType.details, 'admin');
expect(criteria).toEqual([{ fieldName: 'user.name', fieldValue: 'admin' }]);
});

test('returns empty array from criteria if the user type is page but rather an empty array', () => {
const criteria = getCriteriaFromUsersType(UsersType.page, 'admin');
expect(criteria).toEqual([]);
});

test('returns empty array from criteria if the user name is undefined and user type is details', () => {
const criteria = getCriteriaFromUsersType(UsersType.details, undefined);
expect(criteria).toEqual([]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 { UsersType } from '../../../../users/store/model';
import { CriteriaFields } from '../types';

export const getCriteriaFromUsersType = (
type: UsersType,
userName: string | undefined
): CriteriaFields[] => {
if (type === UsersType.details && userName != null) {
return [{ fieldName: 'user.name', fieldValue: userName }];
} else {
return [];
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,31 @@
* 2.0.
*/

import { cloneDeep } from 'lodash/fp';
import { getHostNameFromInfluencers } from './get_host_name_from_influencers';
import { mockAnomalies } from '../mock';

describe('get_host_name_from_influencers', () => {
let anomalies = cloneDeep(mockAnomalies);

beforeEach(() => {
anomalies = cloneDeep(mockAnomalies);
});

test('returns host names from influencers from the mock', () => {
const hostName = getHostNameFromInfluencers(anomalies.anomalies[0].influencers);
expect(hostName).toEqual('zeek-iowa');
expect(getHostNameFromInfluencers(mockAnomalies.anomalies[0].influencers)).toEqual('zeek-iowa');
});

test('returns null if there are no influencers from the mock', () => {
anomalies.anomalies[0].influencers = [];
const hostName = getHostNameFromInfluencers(anomalies.anomalies[0].influencers);
expect(hostName).toEqual(null);
expect(getHostNameFromInfluencers([])).toEqual(null);
});

test('returns null if it is given undefined influencers', () => {
const hostName = getHostNameFromInfluencers();
expect(hostName).toEqual(null);
expect(getHostNameFromInfluencers()).toEqual(null);
});

test('returns null if there influencers is an empty object', () => {
anomalies.anomalies[0].influencers = [{}];
const hostName = getHostNameFromInfluencers(anomalies.anomalies[0].influencers);
expect(hostName).toEqual(null);
expect(getHostNameFromInfluencers([{}])).toEqual(null);
});

test('returns host name mixed with other data', () => {
anomalies.anomalies[0].influencers = [{ 'host.name': 'name-1' }, { 'source.ip': '127.0.0.1' }];
const hostName = getHostNameFromInfluencers(anomalies.anomalies[0].influencers);
const hostName = getHostNameFromInfluencers([
{ 'host.name': 'name-1' },
{ 'source.ip': '127.0.0.1' },
]);
expect(hostName).toEqual('name-1');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,27 @@
* 2.0.
*/

import { cloneDeep } from 'lodash/fp';
import { getNetworkFromInfluencers } from './get_network_from_influencers';
import { mockAnomalies } from '../mock';
import { DestinationOrSource } from '../types';

describe('get_network_from_influencers', () => {
let anomalies = cloneDeep(mockAnomalies);

beforeEach(() => {
anomalies = cloneDeep(mockAnomalies);
});

test('returns null if there are no influencers from the mock', () => {
anomalies.anomalies[0].influencers = [];
const network = getNetworkFromInfluencers(anomalies.anomalies[0].influencers);
expect(network).toEqual(null);
test('returns null if there are no influencers', () => {
expect(getNetworkFromInfluencers([])).toEqual(null);
});

test('returns null if the influencers is an empty object', () => {
anomalies.anomalies[0].influencers = [{}];
const network = getNetworkFromInfluencers(anomalies.anomalies[0].influencers);
expect(network).toEqual(null);
expect(getNetworkFromInfluencers([{}])).toEqual(null);
});

test('returns null if the influencers are undefined', () => {
const network = getNetworkFromInfluencers();
expect(network).toEqual(null);
expect(getNetworkFromInfluencers()).toEqual(null);
});

test('returns network name of source mixed with other data', () => {
anomalies.anomalies[0].influencers = [{ 'host.name': 'name-1' }, { 'source.ip': '127.0.0.1' }];
const network = getNetworkFromInfluencers(anomalies.anomalies[0].influencers);
const network = getNetworkFromInfluencers([
{ 'host.name': 'name-1' },
{ 'source.ip': '127.0.0.1' },
]);
const expected: { ip: string; type: DestinationOrSource } = {
ip: '127.0.0.1',
type: 'source.ip',
Expand All @@ -45,11 +34,10 @@ describe('get_network_from_influencers', () => {
});

test('returns network name mixed with other data', () => {
anomalies.anomalies[0].influencers = [
const network = getNetworkFromInfluencers([
{ 'host.name': 'name-1' },
{ 'destination.ip': '127.0.0.1' },
];
const network = getNetworkFromInfluencers(anomalies.anomalies[0].influencers);
]);
const expected: { ip: string; type: DestinationOrSource } = {
ip: '127.0.0.1',
type: 'destination.ip',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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 { getUserNameFromInfluencers } from './get_user_name_from_influencers';
import { mockAnomalies } from '../mock';

describe('get_user_name_from_influencers', () => {
test('returns user names from influencers from the mock', () => {
expect(getUserNameFromInfluencers(mockAnomalies.anomalies[0].influencers)).toEqual('root');
});

test('returns null if there are no influencers from the mock', () => {
expect(getUserNameFromInfluencers([])).toEqual(null);
});

test('returns null if it is given undefined influencers', () => {
expect(getUserNameFromInfluencers()).toEqual(null);
});

test('returns null if there influencers is an empty object', () => {
expect(getUserNameFromInfluencers([{}])).toEqual(null);
});

test('returns user name mixed with other data', () => {
const userName = getUserNameFromInfluencers([
{ 'user.name': 'root' },
{ 'source.ip': '127.0.0.1' },
]);
expect(userName).toEqual('root');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 { getEntries } from '../get_entries';

export const getUserNameFromInfluencers = (
influencers: Array<Record<string, string>> = [],
userName?: string
): string | null => {
const recordFound = influencers.find((influencer) => {
const [influencerName, influencerValue] = getEntries(influencer);
if (influencerName === 'user.name') {
if (userName == null) {
return true;
} else {
return influencerValue === userName;
}
} else {
return false;
}
});
if (recordFound != null) {
return Object.values(recordFound)[0];
} else {
return null;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ import * as i18n from './translations';
import { getAnomaliesHostTableColumnsCurated } from './get_anomalies_host_table_columns';
import { convertAnomaliesToHosts } from './convert_anomalies_to_hosts';
import { Loader } from '../../loader';
import { getIntervalFromAnomalies } from '../anomaly/get_interval_from_anomalies';
import { AnomaliesHostTableProps } from '../types';
import { useMlCapabilities } from '../hooks/use_ml_capabilities';
import { BasicTable } from './basic_table';
import { hostEquality } from './host_equality';
import { getCriteriaFromHostType } from '../criteria/get_criteria_from_host_type';
import { Panel } from '../../panel';
import { anomaliesTableDefaultEquality } from './default_equality';

const sorting = {
sort: {
Expand All @@ -33,7 +32,6 @@ const sorting = {
const AnomaliesHostTableComponent: React.FC<AnomaliesHostTableProps> = ({
startDate,
endDate,
narrowDateRange,
hostName,
skip,
type,
Expand All @@ -44,18 +42,14 @@ const AnomaliesHostTableComponent: React.FC<AnomaliesHostTableProps> = ({
endDate,
skip,
criteriaFields: getCriteriaFromHostType(type, hostName),
filterQuery: {
exists: { field: 'host.name' },
},
});

const hosts = convertAnomaliesToHosts(tableData, hostName);

const interval = getIntervalFromAnomalies(tableData);
const columns = getAnomaliesHostTableColumnsCurated(
type,
startDate,
endDate,
interval,
narrowDateRange
);
const columns = getAnomaliesHostTableColumnsCurated(type, startDate, endDate);
const pagination = {
initialPageIndex: 0,
initialPageSize: 10,
Expand Down Expand Up @@ -94,4 +88,7 @@ const AnomaliesHostTableComponent: React.FC<AnomaliesHostTableProps> = ({
}
};

export const AnomaliesHostTable = React.memo(AnomaliesHostTableComponent, hostEquality);
export const AnomaliesHostTable = React.memo(
AnomaliesHostTableComponent,
anomaliesTableDefaultEquality
);
Loading