Skip to content

Commit

Permalink
User Page - KPIs and visualisations (#127617)
Browse files Browse the repository at this point in the history
* Create total users visualization

* Organize visualizations
  • Loading branch information
machadoum authored Mar 22, 2022
1 parent b0c3aab commit f4f51e6
Show file tree
Hide file tree
Showing 44 changed files with 748 additions and 256 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const allowedExperimentalValues = Object.freeze({
detectionResponseEnabled: false,
disableIsolationUIPendingStatuses: false,
riskyHostsEnabled: false,
riskyUsersEnabled: false,
securityRulesCancelEnabled: false,
pendingActionResponsesWithAck: true,
policyListEnabled: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ import {
} from './risk_score';
import { UsersQueries } from './users';
import { UserDetailsRequestOptions, UserDetailsStrategyResponse } from './users/details';
import {
TotalUsersKpiRequestOptions,
TotalUsersKpiStrategyResponse,
} from './users/kpi/total_users';

export * from './cti';
export * from './hosts';
Expand Down Expand Up @@ -141,6 +145,8 @@ export type StrategyResponseType<T extends FactoryQueryTypes> = T extends HostsQ
? HostsKpiUniqueIpsStrategyResponse
: T extends UsersQueries.details
? UserDetailsStrategyResponse
: T extends UsersQueries.kpiTotalUsers
? TotalUsersKpiStrategyResponse
: T extends NetworkQueries.details
? NetworkDetailsStrategyResponse
: T extends NetworkQueries.dns
Expand Down Expand Up @@ -199,6 +205,8 @@ export type StrategyRequestType<T extends FactoryQueryTypes> = T extends HostsQu
? HostsKpiUniqueIpsRequestOptions
: T extends UsersQueries.details
? UserDetailsRequestOptions
: T extends UsersQueries.kpiTotalUsers
? TotalUsersKpiRequestOptions
: T extends NetworkQueries.details
? NetworkDetailsRequestOptions
: T extends NetworkQueries.dns
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
* 2.0.
*/

import { TotalUsersKpiStrategyResponse } from './kpi/total_users';

export enum UsersQueries {
details = 'userDetails',
kpiTotalUsers = 'usersKpiTotalUsers',
}

export type UserskKpiStrategyResponse = Omit<TotalUsersKpiStrategyResponse, 'rawResponse'>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* 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 { Maybe } from '../../../..';

export interface KpiHistogramData {
x?: Maybe<number>;
y?: Maybe<number>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common';
import { Inspect, Maybe } from '../../../../common';
import { RequestBasicOptions } from '../../..';
import { KpiHistogramData } from '../common';

export type TotalUsersKpiRequestOptions = RequestBasicOptions;

export interface TotalUsersKpiStrategyResponse extends IEsSearchResponse {
users: Maybe<number>;
usersHistogram: Maybe<KpiHistogramData[]>;
inspect?: Maybe<Inspect>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
import { FIRST_PAGE_SELECTOR, THIRD_PAGE_SELECTOR } from '../../screens/pagination';
import { cleanKibana } from '../../tasks/common';

import { waitForAuthenticationsToBeLoaded } from '../../tasks/hosts/authentications';
import { openAuthentications, openUncommonProcesses } from '../../tasks/hosts/main';
import { waitsForEventsToBeLoaded } from '../../tasks/hosts/events';
import { openEvents, openUncommonProcesses } from '../../tasks/hosts/main';
import { waitForUncommonProcessesToBeLoaded } from '../../tasks/hosts/uncommon_processes';
import { loginAndWaitForPage } from '../../tasks/login';
import { goToFirstPage, goToThirdPage } from '../../tasks/pagination';
Expand Down Expand Up @@ -73,8 +73,8 @@ describe('Pagination', () => {
.first()
.invoke('text')
.then((expectedThirdPageResult) => {
openAuthentications();
waitForAuthenticationsToBeLoaded();
openEvents();
waitsForEventsToBeLoaded();
cy.get(FIRST_PAGE_SELECTOR).should('have.class', 'euiPaginationButton-isActive');
openUncommonProcesses();
waitForUncommonProcessesToBeLoaded();
Expand Down
5 changes: 0 additions & 5 deletions x-pack/plugins/security_solution/cypress/screens/inspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ export const INSPECT_HOSTS_BUTTONS_IN_SECURITY: InspectButtonMetadata[] = [
title: 'All Hosts Table',
tabId: '[data-test-subj="navigation-allHosts"]',
},
{
id: '[data-test-subj="table-authentications-loading-false"]',
title: 'Authentications Table',
tabId: '[data-test-subj="navigation-authentications"]',
},
{
id: '[data-test-subj="table-uncommonProcesses-loading-false"]',
title: 'Uncommon processes Table',
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/security_solution/cypress/tasks/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,8 @@ export const loginAndWaitForTimeline = (timelineId: string, role?: ROLES) => {

export const loginAndWaitForHostDetailsPage = (hostName = 'suricata-iowa') => {
loginAndWaitForPage(hostDetailsUrl(hostName));

cy.get('[data-test-subj="hostDetailsPage"]', { timeout: 12000 }).should('exist');
cy.get('[data-test-subj="loading-spinner"]', { timeout: 12000 }).should('not.exist');
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,19 @@ const hostName = 'siem-window';
describe('Table Navigation', () => {
const mockHasMlUserPermissions = true;
const mockRiskyHostEnabled = true;

const mockProps: TabNavigationProps & RouteSpyState = {
pageName: 'hosts',
pathName: '/hosts',
detailName: undefined,
search: '',
tabName: HostsTableType.authentications,
navTabs: navTabsHostDetails(hostName, mockHasMlUserPermissions, mockRiskyHostEnabled),
navTabs: navTabsHostDetails({
hostName,
hasMlUserPermissions: mockHasMlUserPermissions,
isRiskyHostsEnabled: mockRiskyHostEnabled,
}),

[CONSTANTS.timerange]: {
global: {
[CONSTANTS.timerange]: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { InspectButton } from '../inspect';
import { VisualizationActions, HISTOGRAM_ACTIONS_BUTTON_CLASS } from '../visualization_actions';
import { HoverVisibilityContainer } from '../hover_visibility_container';
import { LensAttributes } from '../visualization_actions/types';
import { UserskKpiStrategyResponse } from '../../../../common/search_strategy/security_solution/users';

const FlexItem = styled(EuiFlexItem)`
min-width: 0;
Expand Down Expand Up @@ -125,12 +126,12 @@ export const barchartConfigs = (config?: { onElementClick?: ElementClickListener

export const addValueToFields = (
fields: StatItem[],
data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse
data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UserskKpiStrategyResponse
): StatItem[] => fields.map((field) => ({ ...field, value: get(field.key, data) }));

export const addValueToAreaChart = (
fields: StatItem[],
data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse
data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UserskKpiStrategyResponse
): ChartSeriesData[] =>
fields
.filter((field) => get(`${field.key}Histogram`, data) != null)
Expand All @@ -142,7 +143,7 @@ export const addValueToAreaChart = (

export const addValueToBarChart = (
fields: StatItem[],
data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse
data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UserskKpiStrategyResponse
): ChartSeriesData[] => {
if (fields.length === 0) return [];
return fields.reduce((acc: ChartSeriesData[], field: StatItem, idx: number) => {
Expand Down Expand Up @@ -171,7 +172,7 @@ export const addValueToBarChart = (

export const useKpiMatrixStatus = (
mappings: Readonly<StatItems[]>,
data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse,
data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UserskKpiStrategyResponse,
id: string,
from: string,
to: string,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* 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 { LensAttributes } from '../../types';

export const kpiTotalUsersAreaLensAttributes: LensAttributes = {
description: '',
state: {
datasourceStates: {
indexpattern: {
layers: {
'416b6fad-1923-4f6a-a2df-b223bb287e30': {
columnOrder: [
'5eea817b-67b7-4268-8ecb-7688d1094721',
'b00c65ea-32be-4163-bfc8-f795b1ef9d06',
],
columns: {
'5eea817b-67b7-4268-8ecb-7688d1094721': {
dataType: 'date',
isBucketed: true,
label: '@timestamp',
operationType: 'date_histogram',
params: { interval: 'auto' },
scale: 'interval',
sourceField: '@timestamp',
},
'b00c65ea-32be-4163-bfc8-f795b1ef9d06': {
customLabel: true,
dataType: 'number',
isBucketed: false,
label: ' ',
operationType: 'unique_count',
scale: 'ratio',
sourceField: 'user.name',
},
},
incompleteColumns: {},
},
},
},
},
filters: [],
query: { language: 'kuery', query: '' },
visualization: {
axisTitlesVisibilitySettings: { x: false, yLeft: false, yRight: false },
fittingFunction: 'None',
gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true },
labelsOrientation: { x: 0, yLeft: 0, yRight: 0 },
layers: [
{
accessors: ['b00c65ea-32be-4163-bfc8-f795b1ef9d06'],
layerId: '416b6fad-1923-4f6a-a2df-b223bb287e30',
layerType: 'data',
seriesType: 'area',
xAccessor: '5eea817b-67b7-4268-8ecb-7688d1094721',
},
],
legend: { isVisible: true, position: 'right' },
preferredSeriesType: 'area',
tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true },
valueLabels: 'hide',
yLeftExtent: { mode: 'full' },
yRightExtent: { mode: 'full' },
},
},
title: '[User] Users - area',
visualizationType: 'lnsXY',
references: [
{
id: '{dataViewId}',
name: 'indexpattern-datasource-current-indexpattern',
type: 'index-pattern',
},
{
id: '{dataViewId}',
name: 'indexpattern-datasource-layer-416b6fad-1923-4f6a-a2df-b223bb287e30',
type: 'index-pattern',
},
],
} as LensAttributes;
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 { LensAttributes } from '../../types';

export const kpiTotalUsersMetricLensAttributes: LensAttributes = {
description: '',
state: {
datasourceStates: {
indexpattern: {
layers: {
'416b6fad-1923-4f6a-a2df-b223bb287e30': {
columnOrder: ['3e51b035-872c-4b44-824b-fe069c222e91'],
columns: {
'3e51b035-872c-4b44-824b-fe069c222e91': {
dataType: 'number',
isBucketed: false,
label: 'Unique count of user.name',
operationType: 'unique_count',
scale: 'ratio',
sourceField: 'user.name',
},
},
incompleteColumns: {},
},
},
},
},
filters: [],
query: { language: 'kuery', query: '' },
visualization: {
accessor: '3e51b035-872c-4b44-824b-fe069c222e91',
layerId: '416b6fad-1923-4f6a-a2df-b223bb287e30',
layerType: 'data',
},
},
title: '[User] Users - metric',
visualizationType: 'lnsMetric',
references: [
{
id: '{dataViewId}',
name: 'indexpattern-datasource-current-indexpattern',
type: 'index-pattern',
},
{
id: '{dataViewId}',
name: 'indexpattern-datasource-layer-416b6fad-1923-4f6a-a2df-b223bb287e30',
type: 'index-pattern',
},
],
} as LensAttributes;
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ export const createStartServicesMock = (
next: jest.fn(),
unsubscribe: jest.fn(),
})),
pipe: jest.fn().mockImplementation(() => ({
subscribe: jest.fn().mockImplementation(() => ({
error: jest.fn(),
next: jest.fn(),
unsubscribe: jest.fn(),
})),
})),
})),
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { kpiUserAuthenticationsBarLensAttributes } from '../../../../common/comp
import { kpiUserAuthenticationsMetricSuccessLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentications_metric_success';
import { kpiUserAuthenticationsMetricFailureLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentication_metric_failure';
import { useHostsKpiAuthentications } from '../../../containers/kpi_hosts/authentications';
import { HostsKpiBaseComponentManage } from '../common';
import { KpiBaseComponentManage } from '../common';
import { HostsKpiProps, HostsKpiChartColors } from '../types';
import * as i18n from './translations';

Expand Down Expand Up @@ -66,7 +66,7 @@ const HostsKpiAuthenticationsComponent: React.FC<HostsKpiProps> = ({
});

return (
<HostsKpiBaseComponentManage
<KpiBaseComponentManage
data={data}
id={id}
inspect={inspect}
Expand Down
Loading

0 comments on commit f4f51e6

Please sign in to comment.