mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
## 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)~
This commit is contained in:
parent
2b2c8a32e1
commit
d5fb64570b
33 changed files with 484 additions and 116 deletions
|
@ -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';
|
||||
|
|
|
@ -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) {
|
||||
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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', () => {
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
@ -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];
|
||||
};
|
||||
|
|
|
@ -21,3 +21,25 @@ exports[`draggable_score renders correctly against snapshot 1`] = `
|
|||
render={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`draggable_score renders correctly against snapshot when the index is not included 1`] = `
|
||||
<Connect(DraggableWrapperComponent)
|
||||
dataProvider={
|
||||
Object {
|
||||
"and": Array [],
|
||||
"enabled": true,
|
||||
"excluded": false,
|
||||
"id": "some-id",
|
||||
"kqlQuery": "",
|
||||
"name": "process.name",
|
||||
"queryMatch": Object {
|
||||
"field": "process.name",
|
||||
"operator": ":",
|
||||
"value": "du",
|
||||
},
|
||||
}
|
||||
}
|
||||
key="some-id"
|
||||
render={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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={{
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
};
|
|
@ -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: {
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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 { convertAnomaliesToNetwork } from './convert_anomalies_to_network';
|
||||
import { BackgroundRefetch } from '../../load_more_table';
|
||||
import { BackgroundRefetch, BasicTableContainer } from '../../load_more_table';
|
||||
import { LoadingPanel } from '../../loading';
|
||||
import { AnomaliesTableProps } from '../types';
|
||||
import { getAnomaliesNetworkTableColumns } from './get_anomalies_network_table_columns';
|
||||
import { AnomaliesNetworkTableProps } from '../types';
|
||||
import { getAnomaliesNetworkTableColumnsCurated } from './get_anomalies_network_table_columns';
|
||||
import { getIntervalFromAnomalies } from '../anomaly/get_interval_from_anomalies';
|
||||
import { getSizeFromAnomalies } from '../anomaly/get_size_from_anomalies';
|
||||
import { dateTimesAreEqual } from './date_time_equality';
|
||||
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: {
|
||||
|
@ -33,20 +29,25 @@ const sorting = {
|
|||
},
|
||||
};
|
||||
|
||||
export const AnomaliesNetworkTable = React.memo<AnomaliesTableProps>(
|
||||
({ startDate, endDate, narrowDateRange, skip, ip }): JSX.Element | null => {
|
||||
export const AnomaliesNetworkTable = React.memo<AnomaliesNetworkTableProps>(
|
||||
({ startDate, endDate, narrowDateRange, skip, ip, type }): JSX.Element | null => {
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const [loading, tableData] = useAnomaliesTableData({
|
||||
influencers: [],
|
||||
startDate,
|
||||
endDate,
|
||||
threshold: 0,
|
||||
skip,
|
||||
});
|
||||
|
||||
const networks = convertAnomaliesToNetwork(tableData, ip);
|
||||
const interval = getIntervalFromAnomalies(tableData);
|
||||
const columns = getAnomaliesNetworkTableColumns(startDate, endDate, interval, narrowDateRange);
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(
|
||||
type,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
const pagination = {
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
|
@ -77,7 +78,7 @@ export const AnomaliesNetworkTable = React.memo<AnomaliesTableProps>(
|
|||
subtitle={`${i18n.SHOWING}: ${networks.length.toLocaleString()} ${i18n.ANOMALIES}`}
|
||||
title={i18n.ANOMALIES}
|
||||
/>
|
||||
<EuiInMemoryTable
|
||||
<BasicTable
|
||||
items={networks}
|
||||
columns={columns}
|
||||
pagination={pagination}
|
||||
|
|
|
@ -4,4 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const getScoreString = (score: number): string => String(Math.ceil(score));
|
||||
import styled from 'styled-components';
|
||||
import { EuiInMemoryTable } from '@elastic/eui';
|
||||
|
||||
export const BasicTable = styled(EuiInMemoryTable)`
|
||||
tbody {
|
||||
th,
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
`;
|
|
@ -6,10 +6,8 @@
|
|||
|
||||
import { AnomaliesByHost, AnomaliesByNetwork } from '../types';
|
||||
|
||||
export const createCompoundHostKey = (anomaliesByHost: AnomaliesByHost): string => {
|
||||
return `${anomaliesByHost.hostName}-${anomaliesByHost.anomaly.entityName}-${anomaliesByHost.anomaly.entityValue}-${anomaliesByHost.anomaly.severity}-${anomaliesByHost.anomaly.jobId}`;
|
||||
};
|
||||
export const createCompoundHostKey = (anomaliesByHost: AnomaliesByHost): string =>
|
||||
`${anomaliesByHost.hostName}-${anomaliesByHost.anomaly.entityName}-${anomaliesByHost.anomaly.entityValue}-${anomaliesByHost.anomaly.severity}-${anomaliesByHost.anomaly.jobId}`;
|
||||
|
||||
export const createCompoundNetworkKey = (anomaliesByNetwork: AnomaliesByNetwork): string => {
|
||||
return `${anomaliesByNetwork.ip}-${anomaliesByNetwork.anomaly.entityName}-${anomaliesByNetwork.anomaly.entityValue}-${anomaliesByNetwork.anomaly.severity}-${anomaliesByNetwork.anomaly.jobId}`;
|
||||
};
|
||||
export const createCompoundNetworkKey = (anomaliesByNetwork: AnomaliesByNetwork): string =>
|
||||
`${anomaliesByNetwork.ip}-${anomaliesByNetwork.anomaly.entityName}-${anomaliesByNetwork.anomaly.entityValue}-${anomaliesByNetwork.anomaly.severity}-${anomaliesByNetwork.anomaly.jobId}`;
|
||||
|
|
|
@ -5,17 +5,17 @@
|
|||
*/
|
||||
|
||||
import { dateTimesAreEqual } from './date_time_equality';
|
||||
import { AnomaliesTableProps } from '../types';
|
||||
import { HostOrNetworkProps } from '../types';
|
||||
|
||||
describe('date_time_equality', () => {
|
||||
test('it returns true if start and end date are equal', () => {
|
||||
const prev: AnomaliesTableProps = {
|
||||
const prev: HostOrNetworkProps = {
|
||||
startDate: new Date('2000').valueOf(),
|
||||
endDate: new Date('2000').valueOf(),
|
||||
narrowDateRange: jest.fn(),
|
||||
skip: false,
|
||||
};
|
||||
const next: AnomaliesTableProps = {
|
||||
const next: HostOrNetworkProps = {
|
||||
startDate: new Date('2000').valueOf(),
|
||||
endDate: new Date('2000').valueOf(),
|
||||
narrowDateRange: jest.fn(),
|
||||
|
@ -26,13 +26,13 @@ describe('date_time_equality', () => {
|
|||
});
|
||||
|
||||
test('it returns false if starts are not equal', () => {
|
||||
const prev: AnomaliesTableProps = {
|
||||
const prev: HostOrNetworkProps = {
|
||||
startDate: new Date('2000').valueOf(),
|
||||
endDate: new Date('1999').valueOf(),
|
||||
narrowDateRange: jest.fn(),
|
||||
skip: false,
|
||||
};
|
||||
const next: AnomaliesTableProps = {
|
||||
const next: HostOrNetworkProps = {
|
||||
startDate: new Date('2000').valueOf(),
|
||||
endDate: new Date('2000').valueOf(),
|
||||
narrowDateRange: jest.fn(),
|
||||
|
@ -43,13 +43,13 @@ describe('date_time_equality', () => {
|
|||
});
|
||||
|
||||
test('it returns false if starts are not equal for next', () => {
|
||||
const prev: AnomaliesTableProps = {
|
||||
const prev: HostOrNetworkProps = {
|
||||
startDate: new Date('2000').valueOf(),
|
||||
endDate: new Date('2000').valueOf(),
|
||||
narrowDateRange: jest.fn(),
|
||||
skip: false,
|
||||
};
|
||||
const next: AnomaliesTableProps = {
|
||||
const next: HostOrNetworkProps = {
|
||||
startDate: new Date('2000').valueOf(),
|
||||
endDate: new Date('1999').valueOf(),
|
||||
narrowDateRange: jest.fn(),
|
||||
|
@ -60,13 +60,13 @@ describe('date_time_equality', () => {
|
|||
});
|
||||
|
||||
test('it returns false if ends are not equal', () => {
|
||||
const prev: AnomaliesTableProps = {
|
||||
const prev: HostOrNetworkProps = {
|
||||
startDate: new Date('2000').valueOf(),
|
||||
endDate: new Date('2001').valueOf(),
|
||||
narrowDateRange: jest.fn(),
|
||||
skip: false,
|
||||
};
|
||||
const next: AnomaliesTableProps = {
|
||||
const next: HostOrNetworkProps = {
|
||||
startDate: new Date('2000').valueOf(),
|
||||
endDate: new Date('2000').valueOf(),
|
||||
narrowDateRange: jest.fn(),
|
||||
|
@ -77,13 +77,13 @@ describe('date_time_equality', () => {
|
|||
});
|
||||
|
||||
test('it returns false if ends are not equal for next', () => {
|
||||
const prev: AnomaliesTableProps = {
|
||||
const prev: HostOrNetworkProps = {
|
||||
startDate: new Date('2000').valueOf(),
|
||||
endDate: new Date('2000').valueOf(),
|
||||
narrowDateRange: jest.fn(),
|
||||
skip: false,
|
||||
};
|
||||
const next: AnomaliesTableProps = {
|
||||
const next: HostOrNetworkProps = {
|
||||
startDate: new Date('2000').valueOf(),
|
||||
endDate: new Date('2001').valueOf(),
|
||||
narrowDateRange: jest.fn(),
|
||||
|
@ -94,13 +94,13 @@ describe('date_time_equality', () => {
|
|||
});
|
||||
|
||||
test('it returns false if skip is not equal', () => {
|
||||
const prev: AnomaliesTableProps = {
|
||||
const prev: HostOrNetworkProps = {
|
||||
startDate: new Date('2000').valueOf(),
|
||||
endDate: new Date('2000').valueOf(),
|
||||
narrowDateRange: jest.fn(),
|
||||
skip: true,
|
||||
};
|
||||
const next: AnomaliesTableProps = {
|
||||
const next: HostOrNetworkProps = {
|
||||
startDate: new Date('2000').valueOf(),
|
||||
endDate: new Date('2000').valueOf(),
|
||||
narrowDateRange: jest.fn(),
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AnomaliesTableProps } from '../types';
|
||||
import { HostOrNetworkProps } from '../types';
|
||||
|
||||
export const dateTimesAreEqual = (
|
||||
prevProps: AnomaliesTableProps,
|
||||
nextProps: AnomaliesTableProps
|
||||
prevProps: HostOrNetworkProps,
|
||||
nextProps: HostOrNetworkProps
|
||||
): boolean =>
|
||||
prevProps.startDate === nextProps.startDate &&
|
||||
prevProps.endDate === nextProps.endDate &&
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { getAnomaliesHostTableColumnsCurated } from './get_anomalies_host_table_columns';
|
||||
import { HostsType } from '../../../store/hosts/model';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const startDate = new Date(2001).valueOf();
|
||||
const endDate = new Date(3000).valueOf();
|
||||
const interval = 'days';
|
||||
const narrowDateRange = jest.fn();
|
||||
|
||||
describe('get_anomalies_host_table_columns', () => {
|
||||
test('on hosts page, we expect to get all columns', () => {
|
||||
expect(
|
||||
getAnomaliesHostTableColumnsCurated(
|
||||
HostsType.page,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
).length
|
||||
).toEqual(6);
|
||||
});
|
||||
|
||||
test('on host details page, we expect to remove one columns', () => {
|
||||
const columns = getAnomaliesHostTableColumnsCurated(
|
||||
HostsType.details,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
expect(columns.length).toEqual(5);
|
||||
});
|
||||
|
||||
test('on host page, we should have Host Name', () => {
|
||||
const columns = getAnomaliesHostTableColumnsCurated(
|
||||
HostsType.page,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
expect(columns.some(col => col.name === i18n.HOST_NAME)).toEqual(true);
|
||||
});
|
||||
|
||||
test('on host details page, we should not have Host Name', () => {
|
||||
const columns = getAnomaliesHostTableColumnsCurated(
|
||||
HostsType.details,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
expect(columns.some(col => col.name === i18n.HOST_NAME)).toEqual(false);
|
||||
});
|
||||
});
|
|
@ -5,7 +5,8 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
|
||||
import moment from 'moment';
|
||||
import { Columns } from '../../load_more_table';
|
||||
import { AnomaliesByHost, Anomaly, NarrowDateRange } from '../types';
|
||||
import { getRowItemDraggable } from '../../tables/helpers';
|
||||
|
@ -14,8 +15,12 @@ import { createCompoundHostKey } from './create_compound_key';
|
|||
import { HostDetailsLink } from '../../links';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { AnomalyScore } from '../score/anomaly_score';
|
||||
import { getEntries } from '../get_entries';
|
||||
import { DraggableScore } from '../score/draggable_score';
|
||||
import { createExplorerLink } from '../links/create_explorer_link';
|
||||
import { LocalizedDateTooltip } from '../../localized_date_tooltip';
|
||||
import { PreferenceFormattedDate } from '../../formatted_date';
|
||||
import { HostsType } from '../../../store/hosts/model';
|
||||
|
||||
export const getAnomaliesHostTableColumns = (
|
||||
startDate: number,
|
||||
|
@ -25,9 +30,10 @@ export const getAnomaliesHostTableColumns = (
|
|||
): [
|
||||
Columns<AnomaliesByHost['hostName'], AnomaliesByHost>,
|
||||
Columns<Anomaly['severity'], AnomaliesByHost>,
|
||||
Columns<Anomaly['jobId'], AnomaliesByHost>,
|
||||
Columns<Anomaly['entityValue'], AnomaliesByHost>,
|
||||
Columns<Anomaly['influencers'], AnomaliesByHost>,
|
||||
Columns<Anomaly['jobId']>
|
||||
Columns<Anomaly['time'], AnomaliesByHost>
|
||||
] => [
|
||||
{
|
||||
name: i18n.HOST_NAME,
|
||||
|
@ -43,17 +49,26 @@ export const getAnomaliesHostTableColumns = (
|
|||
render: item => <HostDetailsLink hostName={item} />,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: i18n.DETECTOR,
|
||||
field: 'anomaly.jobId',
|
||||
sortable: true,
|
||||
render: (jobId, anomaliesByHost) => (
|
||||
<EuiLink
|
||||
href={`${createExplorerLink(anomaliesByHost.anomaly, startDate, endDate)}`}
|
||||
target="_blank"
|
||||
>
|
||||
{jobId}
|
||||
</EuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: i18n.SCORE,
|
||||
field: 'anomaly.severity',
|
||||
sortable: true,
|
||||
render: (_, anomaliesByHost) => (
|
||||
<AnomalyScore
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
jobKey={`anomalies-host-table-severity-${createCompoundHostKey(anomaliesByHost)}`}
|
||||
narrowDateRange={narrowDateRange}
|
||||
interval={interval}
|
||||
<DraggableScore
|
||||
id={`anomalies-host-table-severity-${createCompoundHostKey(anomaliesByHost)}`}
|
||||
score={anomaliesByHost.anomaly}
|
||||
/>
|
||||
),
|
||||
|
@ -104,8 +119,30 @@ export const getAnomaliesHostTableColumns = (
|
|||
),
|
||||
},
|
||||
{
|
||||
name: i18n.DETECTOR,
|
||||
field: 'anomaly.jobId',
|
||||
name: i18n.TIME_STAMP,
|
||||
field: 'anomaly.time',
|
||||
sortable: true,
|
||||
render: time => (
|
||||
<LocalizedDateTooltip date={moment(new Date(time)).toDate()}>
|
||||
<PreferenceFormattedDate value={new Date(time)} />
|
||||
</LocalizedDateTooltip>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export const getAnomaliesHostTableColumnsCurated = (
|
||||
pageType: HostsType,
|
||||
startDate: number,
|
||||
endDate: number,
|
||||
interval: string,
|
||||
narrowDateRange: NarrowDateRange
|
||||
) => {
|
||||
const columns = getAnomaliesHostTableColumns(startDate, endDate, interval, narrowDateRange);
|
||||
|
||||
// Columns to exclude from host details pages
|
||||
if (pageType === 'details') {
|
||||
return columns.filter(column => column.name !== i18n.HOST_NAME);
|
||||
} else {
|
||||
return columns;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { getAnomaliesNetworkTableColumnsCurated } from './get_anomalies_network_table_columns';
|
||||
import { NetworkType } from '../../../store/network/model';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const startDate = new Date(2001).valueOf();
|
||||
const endDate = new Date(3000).valueOf();
|
||||
const interval = 'days';
|
||||
const narrowDateRange = jest.fn();
|
||||
|
||||
describe('get_anomalies_network_table_columns', () => {
|
||||
test('on network page, we expect to get all columns', () => {
|
||||
expect(
|
||||
getAnomaliesNetworkTableColumnsCurated(
|
||||
NetworkType.page,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
).length
|
||||
).toEqual(6);
|
||||
});
|
||||
|
||||
test('on network details page, we expect to remove one columns', () => {
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(
|
||||
NetworkType.details,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
expect(columns.length).toEqual(5);
|
||||
});
|
||||
|
||||
test('on network page, we should have Network Name', () => {
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(
|
||||
NetworkType.page,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
expect(columns.some(col => col.name === i18n.NETWORK_NAME)).toEqual(true);
|
||||
});
|
||||
|
||||
test('on network details page, we should not have Network Name', () => {
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(
|
||||
NetworkType.details,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
expect(columns.some(col => col.name === i18n.NETWORK_NAME)).toEqual(false);
|
||||
});
|
||||
});
|
|
@ -5,7 +5,8 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
|
||||
import moment from 'moment';
|
||||
import { Columns } from '../../load_more_table';
|
||||
import { Anomaly, NarrowDateRange, AnomaliesByNetwork } from '../types';
|
||||
import { getRowItemDraggable } from '../../tables/helpers';
|
||||
|
@ -14,8 +15,12 @@ import { createCompoundNetworkKey } from './create_compound_key';
|
|||
import { IPDetailsLink } from '../../links';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { AnomalyScore } from '../score/anomaly_score';
|
||||
import { getEntries } from '../get_entries';
|
||||
import { DraggableScore } from '../score/draggable_score';
|
||||
import { createExplorerLink } from '../links/create_explorer_link';
|
||||
import { LocalizedDateTooltip } from '../../localized_date_tooltip';
|
||||
import { PreferenceFormattedDate } from '../../formatted_date';
|
||||
import { NetworkType } from '../../../store/network/model';
|
||||
|
||||
export const getAnomaliesNetworkTableColumns = (
|
||||
startDate: number,
|
||||
|
@ -25,9 +30,10 @@ export const getAnomaliesNetworkTableColumns = (
|
|||
): [
|
||||
Columns<AnomaliesByNetwork['ip'], AnomaliesByNetwork>,
|
||||
Columns<Anomaly['severity'], AnomaliesByNetwork>,
|
||||
Columns<Anomaly['jobId'], AnomaliesByNetwork>,
|
||||
Columns<Anomaly['entityValue'], AnomaliesByNetwork>,
|
||||
Columns<Anomaly['influencers'], AnomaliesByNetwork>,
|
||||
Columns<Anomaly['jobId']>
|
||||
Columns<Anomaly['time'], AnomaliesByNetwork>
|
||||
] => [
|
||||
{
|
||||
name: i18n.NETWORK_NAME,
|
||||
|
@ -41,17 +47,26 @@ export const getAnomaliesNetworkTableColumns = (
|
|||
render: item => <IPDetailsLink ip={item} />,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: i18n.DETECTOR,
|
||||
field: 'anomaly.jobId',
|
||||
sortable: true,
|
||||
render: (jobId, anomaliesByHost) => (
|
||||
<EuiLink
|
||||
href={`${createExplorerLink(anomaliesByHost.anomaly, startDate, endDate)}`}
|
||||
target="_blank"
|
||||
>
|
||||
{jobId}
|
||||
</EuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: i18n.SCORE,
|
||||
field: 'anomaly.severity',
|
||||
sortable: true,
|
||||
render: (_, anomaliesByNetwork) => (
|
||||
<AnomalyScore
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
jobKey={`anomalies-network-table-severity-${createCompoundNetworkKey(anomaliesByNetwork)}`}
|
||||
narrowDateRange={narrowDateRange}
|
||||
interval={interval}
|
||||
<DraggableScore
|
||||
id={`anomalies-network-table-severity-${createCompoundNetworkKey(anomaliesByNetwork)}`}
|
||||
score={anomaliesByNetwork.anomaly}
|
||||
/>
|
||||
),
|
||||
|
@ -98,8 +113,30 @@ export const getAnomaliesNetworkTableColumns = (
|
|||
),
|
||||
},
|
||||
{
|
||||
name: i18n.DETECTOR,
|
||||
field: 'anomaly.jobId',
|
||||
name: i18n.TIME_STAMP,
|
||||
field: 'anomaly.time',
|
||||
sortable: true,
|
||||
render: time => (
|
||||
<LocalizedDateTooltip date={moment(new Date(time)).toDate()}>
|
||||
<PreferenceFormattedDate value={new Date(time)} />
|
||||
</LocalizedDateTooltip>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export const getAnomaliesNetworkTableColumnsCurated = (
|
||||
pageType: NetworkType,
|
||||
startDate: number,
|
||||
endDate: number,
|
||||
interval: string,
|
||||
narrowDateRange: NarrowDateRange
|
||||
) => {
|
||||
const columns = getAnomaliesNetworkTableColumns(startDate, endDate, interval, narrowDateRange);
|
||||
|
||||
// Columns to exclude from ip details pages
|
||||
if (pageType === 'details') {
|
||||
return columns.filter(column => column.name !== i18n.NETWORK_NAME);
|
||||
} else {
|
||||
return columns;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,7 +19,7 @@ export const LOADING = i18n.translate('xpack.siem.ml.table.loadingDescription',
|
|||
});
|
||||
|
||||
export const SCORE = i18n.translate('xpack.siem.ml.table.scoreTitle', {
|
||||
defaultMessage: 'Score',
|
||||
defaultMessage: 'Anomaly Score',
|
||||
});
|
||||
|
||||
export const HOST_NAME = i18n.translate('xpack.siem.ml.table.hostNameTitle', {
|
||||
|
@ -35,9 +35,13 @@ export const ENTITY = i18n.translate('xpack.siem.ml.table.entityTitle', {
|
|||
});
|
||||
|
||||
export const DETECTOR = i18n.translate('xpack.siem.ml.table.detectorTitle', {
|
||||
defaultMessage: 'Detector',
|
||||
defaultMessage: 'Job Name',
|
||||
});
|
||||
|
||||
export const NETWORK_NAME = i18n.translate('xpack.siem.ml.table.networkNameTitle', {
|
||||
defaultMessage: 'Network IP',
|
||||
});
|
||||
|
||||
export const TIME_STAMP = i18n.translate('xpack.siem.ml.table.timestampTitle', {
|
||||
defaultMessage: 'Timestamp',
|
||||
});
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { HostsType } from '../../store/hosts/model';
|
||||
import { NetworkType } from '../../store/network/model';
|
||||
|
||||
export interface Influencer {
|
||||
influencer_field_name: string;
|
||||
influencer_field_values: string[];
|
||||
|
@ -73,15 +76,23 @@ export interface AnomaliesByNetwork {
|
|||
anomaly: Anomaly;
|
||||
}
|
||||
|
||||
export interface AnomaliesTableProps {
|
||||
export interface HostOrNetworkProps {
|
||||
startDate: number;
|
||||
endDate: number;
|
||||
narrowDateRange: NarrowDateRange;
|
||||
skip: boolean;
|
||||
hostName?: string;
|
||||
ip?: string;
|
||||
}
|
||||
|
||||
export type AnomaliesHostTableProps = HostOrNetworkProps & {
|
||||
hostName?: string;
|
||||
type: HostsType;
|
||||
};
|
||||
|
||||
export type AnomaliesNetworkTableProps = HostOrNetworkProps & {
|
||||
ip?: string;
|
||||
type: NetworkType;
|
||||
};
|
||||
|
||||
export interface MlCapabilities {
|
||||
capabilities: {
|
||||
canGetJobs: boolean;
|
||||
|
|
|
@ -23,10 +23,10 @@ export const useJobSummaryData = (jobIds: string[], refetchSummaryData = false):
|
|||
const [loading, setLoading] = useState(true);
|
||||
const config = useContext(KibanaConfigContext);
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const userPermissions = hasMlUserPermissions(capabilities);
|
||||
|
||||
const fetchFunc = async () => {
|
||||
if (jobIds.length > 0) {
|
||||
const userPermissions = hasMlUserPermissions(capabilities);
|
||||
if (userPermissions) {
|
||||
const data: Job[] = await jobsSummary(jobIds, {
|
||||
'kbn-version': config.kbnVersion,
|
||||
|
@ -44,7 +44,7 @@ export const useJobSummaryData = (jobIds: string[], refetchSummaryData = false):
|
|||
useEffect(() => {
|
||||
setLoading(true);
|
||||
fetchFunc();
|
||||
}, [jobIds.join(','), refetchSummaryData]);
|
||||
}, [jobIds.join(','), refetchSummaryData, userPermissions]);
|
||||
|
||||
return [loading, jobSummaryData];
|
||||
};
|
||||
|
|
|
@ -23,9 +23,9 @@ export const useSiemJobs = (refetchData: boolean): Return => {
|
|||
const [loading, setLoading] = useState(true);
|
||||
const config = useContext(KibanaConfigContext);
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const userPermissions = hasMlUserPermissions(capabilities);
|
||||
|
||||
const fetchFunc = async () => {
|
||||
const userPermissions = hasMlUserPermissions(capabilities);
|
||||
if (userPermissions) {
|
||||
const data = await groupsData({
|
||||
'kbn-version': config.kbnVersion,
|
||||
|
@ -41,7 +41,7 @@ export const useSiemJobs = (refetchData: boolean): Return => {
|
|||
useEffect(() => {
|
||||
setLoading(true);
|
||||
fetchFunc();
|
||||
}, [refetchData]);
|
||||
}, [refetchData, userPermissions]);
|
||||
|
||||
return [loading, siemJobs];
|
||||
};
|
||||
|
|
|
@ -11,9 +11,7 @@ import * as React from 'react';
|
|||
import { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
|
||||
import { apolloClientObservable, mockGlobalState } from '../../../../mock';
|
||||
import { AuthenticationsEdges } from '../../../../graphql/types';
|
||||
import { createStore, hostsModel, State } from '../../../../store';
|
||||
import { Columns } from '../../../load_more_table';
|
||||
|
||||
import { mockData } from './mock';
|
||||
import * as i18n from './translations';
|
||||
|
@ -60,22 +58,24 @@ describe('Authentication Table Component', () => {
|
|||
expect(columns.length).toEqual(7);
|
||||
});
|
||||
|
||||
test('on host details page, we should have Last Failed Destination column', () => {
|
||||
const columns = getAuthenticationColumnsCurated(hostsModel.HostsType.page);
|
||||
expect(columns.some(col => col.name === i18n.LAST_FAILED_DESTINATION)).toEqual(true);
|
||||
});
|
||||
|
||||
test('on host details page, we should not have Last Failed Destination column', () => {
|
||||
const columns = getAuthenticationColumnsCurated(hostsModel.HostsType.details);
|
||||
expect(
|
||||
columns.includes(
|
||||
(col: Columns<AuthenticationsEdges>) => col.name === i18n.LAST_FAILED_DESTINATION
|
||||
)
|
||||
).toEqual(false);
|
||||
expect(columns.some(col => col.name === i18n.LAST_FAILED_DESTINATION)).toEqual(false);
|
||||
});
|
||||
|
||||
test('on host page, we should have Last Successful Destination column', () => {
|
||||
const columns = getAuthenticationColumnsCurated(hostsModel.HostsType.page);
|
||||
expect(columns.some(col => col.name === i18n.LAST_SUCCESSFUL_DESTINATION)).toEqual(true);
|
||||
});
|
||||
|
||||
test('on host details page, we should not have Last Successful Destination column', () => {
|
||||
const columns = getAuthenticationColumnsCurated(hostsModel.HostsType.details);
|
||||
expect(
|
||||
columns.includes(
|
||||
(col: Columns<AuthenticationsEdges>) => col.name === i18n.LAST_SUCCESSFUL_DESTINATION
|
||||
)
|
||||
).toEqual(false);
|
||||
expect(columns.some(col => col.name === i18n.LAST_SUCCESSFUL_DESTINATION)).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,9 +11,7 @@ import * as React from 'react';
|
|||
import { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
|
||||
import { apolloClientObservable, mockGlobalState } from '../../../../mock';
|
||||
import { EcsEdges } from '../../../../graphql/types';
|
||||
import { createStore, hostsModel, State } from '../../../../store';
|
||||
import { Columns } from '../../../load_more_table';
|
||||
|
||||
import { EventsTable, getEventsColumnsCurated } from '.';
|
||||
import { mockData } from './mock';
|
||||
|
@ -61,11 +59,14 @@ describe('Load More Events Table Component', () => {
|
|||
expect(columns.length).toEqual(7);
|
||||
});
|
||||
|
||||
test('on host page, we should have Host Name column', () => {
|
||||
const columns = getEventsColumnsCurated(hostsModel.HostsType.page);
|
||||
expect(columns.some(col => col.name === i18n.HOST_NAME)).toEqual(true);
|
||||
});
|
||||
|
||||
test('on host details page, we should not have Host Name column', () => {
|
||||
const columns = getEventsColumnsCurated(hostsModel.HostsType.details);
|
||||
expect(columns.includes((col: Columns<EcsEdges>) => col.name === i18n.HOST_NAME)).toEqual(
|
||||
false
|
||||
);
|
||||
expect(columns.some(col => col.name === i18n.HOST_NAME)).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ import * as ReactDOM from 'react-dom';
|
|||
|
||||
import { UIRoutes as KibanaUIRoutes } from 'ui/routes';
|
||||
|
||||
import { DEFAULT_INDEX_KEY, DEFAULT_ANOMALY_SCORE } from '../../../../common/constants';
|
||||
import {
|
||||
AppBufferedKibanaServiceCall,
|
||||
AppFrameworkAdapter,
|
||||
|
@ -31,6 +32,7 @@ export class AppKibanaFrameworkAdapter implements AppFrameworkAdapter {
|
|||
public dateFormatTz?: string;
|
||||
public darkMode?: boolean;
|
||||
public indexPattern?: string;
|
||||
public anomalyScore?: number;
|
||||
public kbnVersion?: string;
|
||||
public scaledDateFormat?: string;
|
||||
public timezone?: string;
|
||||
|
@ -143,7 +145,8 @@ export class AppKibanaFrameworkAdapter implements AppFrameworkAdapter {
|
|||
} catch (e) {
|
||||
this.darkMode = false;
|
||||
}
|
||||
this.indexPattern = config.get('siem:defaultIndex');
|
||||
this.indexPattern = config.get(DEFAULT_INDEX_KEY);
|
||||
this.anomalyScore = config.get(DEFAULT_ANOMALY_SCORE);
|
||||
this.scaledDateFormat = config.get('dateFormat:scaled');
|
||||
});
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ export class AppTestingFrameworkAdapter implements AppFrameworkAdapter {
|
|||
public dateFormat?: string;
|
||||
public dateFormatTz?: string;
|
||||
public indexPattern?: string;
|
||||
public anomalyScore?: number;
|
||||
public kbnVersion?: string;
|
||||
public scaledDateFormat?: string;
|
||||
public timezone?: string;
|
||||
|
|
|
@ -27,6 +27,7 @@ export interface AppFrameworkAdapter {
|
|||
dateFormatTz?: string;
|
||||
darkMode?: boolean;
|
||||
indexPattern?: string;
|
||||
anomalyScore?: number;
|
||||
kbnVersion?: string;
|
||||
scaledDateFormat?: string;
|
||||
timezone?: string;
|
||||
|
|
|
@ -233,6 +233,7 @@ const HostDetailsComponent = pure<HostDetailsComponentProps>(
|
|||
endDate={to}
|
||||
skip={isInitializing}
|
||||
hostName={hostName}
|
||||
type={type}
|
||||
narrowDateRange={(score, interval) => {
|
||||
const fromTo = scoreIntervalToDateTime(score, interval);
|
||||
setAbsoluteRangeDatePicker({
|
||||
|
|
|
@ -214,6 +214,7 @@ const HostsComponent = pure<HostsComponentProps>(({ filterQuery, setAbsoluteRang
|
|||
startDate={from}
|
||||
endDate={to}
|
||||
skip={isInitializing}
|
||||
type={hostsModel.HostsType.page}
|
||||
narrowDateRange={(score, interval) => {
|
||||
const fromTo = scoreIntervalToDateTime(score, interval);
|
||||
setAbsoluteRangeDatePicker({
|
||||
|
|
|
@ -260,6 +260,7 @@ export const IPDetailsComponent = pure<IPDetailsComponentProps>(
|
|||
endDate={to}
|
||||
skip={isInitializing}
|
||||
ip={ip}
|
||||
type={networkModel.NetworkType.details}
|
||||
narrowDateRange={(score, interval) => {
|
||||
const fromTo = scoreIntervalToDateTime(score, interval);
|
||||
setAbsoluteRangeDatePicker({
|
||||
|
|
|
@ -168,6 +168,7 @@ const NetworkComponent = pure<NetworkComponentProps>(
|
|||
startDate={from}
|
||||
endDate={to}
|
||||
skip={isInitializing}
|
||||
type={networkModel.NetworkType.page}
|
||||
narrowDateRange={(score, interval) => {
|
||||
const fromTo = scoreIntervalToDateTime(score, interval);
|
||||
setAbsoluteRangeDatePicker({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue