Skip to content

Commit

Permalink
[SIEM] Anomaly UI table changes (#40440) (#40938)
Browse files Browse the repository at this point in the history
## Summary

- [x] Changed "Score" to "Anomaly Score"
- [x] Changed "Detector" to be "Job Name"
- [x] Added Link from "Job Name" to Anomaly Explorer page
- [x] Aligned text of the tables to be at the top
- [x] Removed the Information I from the table rows but kept it on the Host Details and Network Details
- [x] Added Timestamp to the end of the table
- [x] Moved "Job Name" to be after "Anomaly Score"
- [x] Removed Host Name from Anomalies table when on the Host Details page as it is redundant
- [x] Removed Network Name from Anomalies table when on the Network Details page as it is redundant
- [x] Added anomaly score Default threshold of 50 for the advanced settings page

Advanced setting for default Anomaly Score:
<img width="1225" alt="Screen Shot 2019-07-05 at 6 15 31 PM" src="https://user-images.githubusercontent.com/1151048/60749093-5abd4980-9f52-11e9-9340-08ef8e462c8f.png">

Before Host Overview:
<img width="2192" alt="before-overview-hosts" src="https://user-images.githubusercontent.com/1151048/60746932-23916d00-9f3f-11e9-81fb-e3dba98af160.png">

After Host Overview:
<img width="2186" alt="after-overview-hosts" src="https://user-images.githubusercontent.com/1151048/60746938-2f7d2f00-9f3f-11e9-9a4c-37f5bbc19771.png">

Before Host Details:
<img width="2201" alt="before-host-details" src="https://user-images.githubusercontent.com/1151048/60746961-4f145780-9f3f-11e9-9086-2709b7957221.png">

After Host Details:
<img width="2202" alt="after-host-details" src="https://user-images.githubusercontent.com/1151048/60746969-56d3fc00-9f3f-11e9-9110-5fb46fb398c9.png">


Before Network Overview:
<img width="2199" alt="before-network-overivew" src="https://user-images.githubusercontent.com/1151048/60746954-41f76880-9f3f-11e9-8c75-cc7e6dbde276.png">

After Network Overview:
<img width="2196" alt="after-network-overview" src="https://user-images.githubusercontent.com/1151048/60746957-47ed4980-9f3f-11e9-843a-a2b210347649.png">

Before Network Details:
<img width="2200" alt="before-network-details" src="https://user-images.githubusercontent.com/1151048/60746972-5d627380-9f3f-11e9-8dcb-cc1e1d73c0f9.png">

After Network Details:
<img width="2189" alt="after-network-details" src="https://user-images.githubusercontent.com/1151048/60746974-63585480-9f3f-11e9-9847-4645a7b1ab1d.png">

### Checklist

Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR.

- [x] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)
- [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)
- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials
- [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios
- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)

### For maintainers

- [x] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)
~- [ ] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~
  • Loading branch information
FrankHassanabad authored Jul 12, 2019
1 parent d26c406 commit fbfd67d
Show file tree
Hide file tree
Showing 33 changed files with 483 additions and 115 deletions.
1 change: 1 addition & 0 deletions x-pack/legacy/plugins/siem/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
export const APP_ID = 'siem';
export const APP_NAME = 'SIEM';
export const DEFAULT_INDEX_KEY = 'siem:defaultIndex';
export const DEFAULT_ANOMALY_SCORE = 'siem:defaultAnomalyScore';
15 changes: 14 additions & 1 deletion x-pack/legacy/plugins/siem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Server } from 'hapi';
import { initServerWithKibana } from './server/kibana.index';
import { savedObjectMappings } from './server/saved_objects';

import { APP_ID, APP_NAME, DEFAULT_INDEX_KEY } from './common/constants';
import { APP_ID, APP_NAME, DEFAULT_INDEX_KEY, DEFAULT_ANOMALY_SCORE } from './common/constants';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function siem(kibana: any) {
Expand Down Expand Up @@ -56,6 +56,19 @@ export function siem(kibana: any) {
category: ['siem'],
requiresPageReload: true,
},
[DEFAULT_ANOMALY_SCORE]: {
name: i18n.translate('xpack.siem.uiSettings.defaultAnomalyScoreLabel', {
defaultMessage: 'Default anomaly threshold',
}),
value: 50,
type: 'number',
description: i18n.translate('xpack.siem.uiSettings.defaultAnomalyScoreDescription', {
defaultMessage:
'Default anomaly score threshold to exceed before showing anomalies. Valid values are between 0 and 100',
}),
category: ['siem'],
requiresPageReload: true,
},
},
mappings: savedObjectMappings,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
*/

import { InfluencerInput } from '../types';
import { influencersToString } from './use_anomalies_table_data';
import { influencersToString, getThreshold } from './use_anomalies_table_data';
import { AppKibanaFrameworkAdapter } from '../../../lib/adapters/framework/kibana_framework_adapter';

describe('use_anomalies_table_data', () => {
test('should return a reduced single influencer to string', () => {
Expand Down Expand Up @@ -44,4 +45,46 @@ describe('use_anomalies_table_data', () => {
const influencerString = influencersToString(null);
expect(influencerString).toEqual('');
});

describe('#getThreshold', () => {
test('should return 0 if given something below -1', () => {
const config: Partial<AppKibanaFrameworkAdapter> = {
anomalyScore: -100,
};
expect(getThreshold(config, -1)).toEqual(0);
});

test('should return 100 if given something above 100', () => {
const config: Partial<AppKibanaFrameworkAdapter> = {
anomalyScore: 1000,
};
expect(getThreshold(config, -1)).toEqual(100);
});

test('should return overridden value if passed in as non negative 1', () => {
const config: Partial<AppKibanaFrameworkAdapter> = {
anomalyScore: 75,
};
expect(getThreshold(config, 50)).toEqual(50);
});

test('should return 50 if no anomalyScore was set', () => {
const config: Partial<AppKibanaFrameworkAdapter> = {};
expect(getThreshold(config, -1)).toEqual(50);
});

test('should return custom setting', () => {
const config: Partial<AppKibanaFrameworkAdapter> = {
anomalyScore: 75,
};
expect(getThreshold(config, -1)).toEqual(75);
});

test('should round down a value up if sent in a floating point number', () => {
const config: Partial<AppKibanaFrameworkAdapter> = {
anomalyScore: 75.01,
};
expect(getThreshold(config, -1)).toEqual(75);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -40,31 +40,48 @@ export const getTimeZone = (config: Partial<AppKibanaFrameworkAdapter>): string
}
};

export const getThreshold = (
config: Partial<AppKibanaFrameworkAdapter>,
threshold: number
): number => {
if (threshold !== -1) {
return threshold;
} else if (config.anomalyScore == null) {
return 50;
} else if (config.anomalyScore < 0) {
return 0;
} else if (config.anomalyScore > 100) {
return 100;
} else {
return Math.floor(config.anomalyScore);
}
};

export const useAnomaliesTableData = ({
influencers,
startDate,
endDate,
threshold = 0,
threshold = -1,
skip = false,
}: Args): Return => {
const [tableData, setTableData] = useState<Anomalies | null>(null);
const [loading, setLoading] = useState(true);
const config = useContext(KibanaConfigContext);
const capabilities = useContext(MlCapabilitiesContext);
const userPermissions = hasMlUserPermissions(capabilities);

const fetchFunc = async (
influencersInput: InfluencerInput[] | null,
earliestMs: number,
latestMs: number
) => {
const userPermissions = hasMlUserPermissions(capabilities);
if (userPermissions && influencersInput != null && !skip) {
const data = await anomaliesTableData(
{
jobIds: [],
criteriaFields: [],
aggregationInterval: 'auto',
threshold,
threshold: getThreshold(config, threshold),
earliestMs,
latestMs,
influencers: influencersInput,
Expand All @@ -89,7 +106,7 @@ export const useAnomaliesTableData = ({
useEffect(() => {
setLoading(true);
fetchFunc(influencers, startDate, endDate);
}, [influencersToString(influencers), startDate, endDate, skip]);
}, [influencersToString(influencers), startDate, endDate, skip, userPermissions]);

return [loading, tableData];
};

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { EuiText, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic
import React from 'react';
import styled from 'styled-components';
import { Anomaly, NarrowDateRange } from '../types';
import { getScoreString } from './get_score_string';
import { getScoreString } from './score_health';
import { PreferenceFormattedDate } from '../../formatted_date';
import { createInfluencers } from './../influencers/create_influencers';
import { DescriptionList } from '../../../../common/utility_types';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ describe('draggable_score', () => {
);
expect(toJson(wrapper)).toMatchSnapshot();
});

test('renders correctly against snapshot when the index is not included', () => {
const wrapper = shallow(<DraggableScore id="some-id" score={anomalies.anomalies[0]} />);
expect(toJson(wrapper)).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import { Anomaly } from '../types';
import { IS_OPERATOR } from '../../timeline/data_providers/data_provider';
import { Provider } from '../../timeline/data_providers/provider';
import { Spacer } from '../../page';
import { getScoreString } from './get_score_string';
import { getScoreString } from './score_health';

export const DraggableScore = React.memo<{
id: string;
index: number;
index?: number;
score: Anomaly;
}>(
({ id, index, score }): JSX.Element => (
({ id, index = 0, score }): JSX.Element => (
<DraggableWrapper
key={id}
dataProvider={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { getScoreString } from './get_score_string';
import { getScoreString } from './score_health';

describe('create_influencers', () => {
test('it rounds up to 1 from 0.3', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { EuiHealth } from '@elastic/eui';

interface Props {
score: number;
}

export const getScoreString = (score: number) => String(Math.ceil(score));

export const ScoreHealth = React.memo<Props>(({ score }) => {
const scoreCeiling = getScoreString(score);
const color = getSeverityColor(score);
return <EuiHealth color={color}>{scoreCeiling}</EuiHealth>;
});

// ಠ_ಠ A hard-fork of the `ml` ml/common/util/anomaly_utils.js#getSeverityColor ಠ_ಠ
//
// Returns a severity label (one of critical, major, minor, warning, low or unknown)
// for the supplied normalized anomaly score (a value between 0 and 100), where scores
// less than 3 are assigned a severity of 'low'.
export const getSeverityColor = (normalizedScore: number): string => {
if (normalizedScore >= 75) {
return '#fe5050';
} else if (normalizedScore >= 50) {
return '#fba740';
} else if (normalizedScore >= 25) {
return '#fdec25';
} else if (normalizedScore >= 3) {
return '#8bc8fb';
} else if (normalizedScore >= 0) {
return '#d2e9f7';
} else {
return '#ffffff';
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,22 @@
*/

import React, { useContext } from 'react';
import { EuiInMemoryTable, EuiPanel } from '@elastic/eui';
import styled from 'styled-components';
import { EuiPanel } from '@elastic/eui';
import { useAnomaliesTableData } from '../anomaly/use_anomalies_table_data';
import { HeaderPanel } from '../../header_panel';

import * as i18n from './translations';
import { getAnomaliesHostTableColumns } from './get_anomalies_host_table_columns';
import { getAnomaliesHostTableColumnsCurated } from './get_anomalies_host_table_columns';
import { convertAnomaliesToHosts } from './convert_anomalies_to_hosts';
import { BackgroundRefetch } from '../../load_more_table';
import { BackgroundRefetch, BasicTableContainer } from '../../load_more_table';
import { LoadingPanel } from '../../loading';
import { getIntervalFromAnomalies } from '../anomaly/get_interval_from_anomalies';
import { getSizeFromAnomalies } from '../anomaly/get_size_from_anomalies';
import { dateTimesAreEqual } from './date_time_equality';
import { AnomaliesTableProps } from '../types';
import { AnomaliesHostTableProps } from '../types';
import { hasMlUserPermissions } from '../permissions/has_ml_user_permissions';
import { MlCapabilitiesContext } from '../permissions/ml_capabilities_provider';

const BasicTableContainer = styled.div`
position: relative;
`;
import { BasicTable } from './basic_table';

const sorting = {
sort: {
Expand All @@ -33,20 +29,25 @@ const sorting = {
},
};

export const AnomaliesHostTable = React.memo<AnomaliesTableProps>(
({ startDate, endDate, narrowDateRange, hostName, skip }): JSX.Element | null => {
export const AnomaliesHostTable = React.memo<AnomaliesHostTableProps>(
({ startDate, endDate, narrowDateRange, hostName, skip, type }): JSX.Element | null => {
const capabilities = useContext(MlCapabilitiesContext);
const [loading, tableData] = useAnomaliesTableData({
influencers: [],
startDate,
endDate,
threshold: 0,
skip,
});

const hosts = convertAnomaliesToHosts(tableData, hostName);
const interval = getIntervalFromAnomalies(tableData);
const columns = getAnomaliesHostTableColumns(startDate, endDate, interval, narrowDateRange);
const columns = getAnomaliesHostTableColumnsCurated(
type,
startDate,
endDate,
interval,
narrowDateRange
);
const pagination = {
pageIndex: 0,
pageSize: 10,
Expand Down Expand Up @@ -77,12 +78,7 @@ export const AnomaliesHostTable = React.memo<AnomaliesTableProps>(
subtitle={`${i18n.SHOWING}: ${hosts.length.toLocaleString()} ${i18n.ANOMALIES}`}
title={i18n.ANOMALIES}
/>
<EuiInMemoryTable
items={hosts}
columns={columns}
pagination={pagination}
sorting={sorting}
/>
<BasicTable items={hosts} columns={columns} pagination={pagination} sorting={sorting} />
</BasicTableContainer>
</EuiPanel>
);
Expand Down
Loading

0 comments on commit fbfd67d

Please sign in to comment.