Skip to content

Commit

Permalink
[Security Solution] Fix: destination of the command link is a host de…
Browse files Browse the repository at this point in the history
…tails page (#188742)

## Summary
It doesn't look right that the destination of the command link is a host
details page.
In this PR the command link has been removed and was replaced with a
normal text.
The issue related with this matter is below:
#188295

- Before:


https://github.com/user-attachments/assets/78d4a09e-e531-4722-b6af-fe7068b29ad5

- Now:

<img width="399" alt="Screenshot 2024-07-19 at 14 16 05"
src="https://github.com/user-attachments/assets/8f8b51e1-3aa6-4d00-8ebc-a98db4afaef0">

### Checklist

Delete any items that are not applicable to this PR.

- [ ] [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: Elastic Machine <[email protected]>
  • Loading branch information
agusruidiazgd and elasticmachine authored Jul 23, 2024
1 parent c11d534 commit abfd30d
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 172 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import type {
NetworkTopCountriesColumnsNetworkDetails,
} from '../../network/components/network_top_countries_table/columns';
import type { TlsColumns } from '../../network/components/tls_table/columns';
import type { UncommonProcessTableColumns } from '../../hosts/components/uncommon_process_table';
import type { UncommonProcessTableColumns } from '../../hosts/components/uncommon_process_table/columns';
import type { HostRiskScoreColumns } from '../../../entity_analytics/components/host_risk_score_table';

import type { UsersColumns } from '../../network/components/users_table/columns';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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 { getOr } from 'lodash/fp';
import React from 'react';

import { hostsModel } from '../../store';

import { mockData } from './mock';
import { HostsType } from '../../store/model';
import * as i18n from './translations';
import { getUncommonColumnsCurated, getHostNames } from './columns';

jest.mock('../../../../common/lib/kibana');

jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
...original,
EuiScreenReaderOnly: () => <></>,
};
});

jest.mock('../../../../common/components/link_to');

describe('Uncommon Process Columns', () => {
const loadPage = jest.fn();

const defaultProps = {
data: mockData.edges,
fakeTotalCount: getOr(50, 'fakeTotalCount', mockData.pageInfo),
id: 'uncommonProcess',
isInspect: false,
loading: false,
loadPage,
setQuerySkip: jest.fn(),
showMorePagesIndicator: getOr(false, 'showMorePagesIndicator', mockData.pageInfo),
totalCount: mockData.totalCount,
type: hostsModel.HostsType.page,
};

describe('#getHostNames', () => {
test('when hosts is an empty array, it should return an empty array', () => {
const hostNames = getHostNames(defaultProps.data[0].node.hosts);
expect(hostNames.length).toEqual(0);
});
test('when hosts is an array with one elem, it should return an array with the name property of the item', () => {
const hostNames = getHostNames(defaultProps.data[1].node.hosts);
expect(hostNames.length).toEqual(1);
});
test('when hosts is an array with two elem, it should return an array with each name of each item', () => {
const hostNames = getHostNames(defaultProps.data[2].node.hosts);
expect(hostNames.length).toEqual(2);
});
test('when hosts is an array with items without name prop, it should return an empty array', () => {
const hostNames = getHostNames(defaultProps.data[3].node.hosts);
expect(hostNames.length).toEqual(0);
});
});

describe('#getUncommonColumnsCurated', () => {
test('on hosts page, we expect to get all columns', () => {
expect(getUncommonColumnsCurated(HostsType.page).length).toEqual(6);
});

test('on host details page, we expect to remove two columns', () => {
const columns = getUncommonColumnsCurated(HostsType.details);
expect(columns.length).toEqual(4);
});

test('on host page, we should have hosts', () => {
const columns = getUncommonColumnsCurated(HostsType.page);
expect(columns.some((col) => col.name === i18n.HOSTS)).toEqual(true);
});

test('on host page, we should have number of hosts', () => {
const columns = getUncommonColumnsCurated(HostsType.page);
expect(columns.some((col) => col.name === i18n.NUMBER_OF_HOSTS)).toEqual(true);
});

test('on host details page, we should not have hosts', () => {
const columns = getUncommonColumnsCurated(HostsType.details);
expect(columns.some((col) => col.name === i18n.HOSTS)).toEqual(false);
});

test('on host details page, we should not have number of hosts', () => {
const columns = getUncommonColumnsCurated(HostsType.details);
expect(columns.some((col) => col.name === i18n.NUMBER_OF_HOSTS)).toEqual(false);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* 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 React from 'react';
import type { HostEcs } from '@kbn/securitysolution-ecs';
import type { Columns } from '../../../components/paginated_table';
import { HostDetailsLink } from '../../../../common/components/links';
import { defaultToEmptyTag, getEmptyValue } from '../../../../common/components/empty_value';
import { getRowItemsWithActions } from '../../../../common/components/tables/helpers';
import type { HostsUncommonProcessesEdges } from '../../../../../common/search_strategy';
import { HostsType } from '../../store/model';
import * as i18n from './translations';

export type UncommonProcessTableColumns = Array<Columns<HostsUncommonProcessesEdges>>;

export const getHostNames = (hosts: HostEcs[]): string[] => {
if (!hosts) return [];
return hosts
.filter((host) => host.name != null && host.name[0] != null)
.map((host) => (host.name != null && host.name[0] != null ? host.name[0] : ''));
};

export const getUncommonColumns = (): UncommonProcessTableColumns => [
{
name: i18n.NAME,
truncateText: false,
mobileOptions: { show: true },
width: '20%',
render: ({ node }) =>
getRowItemsWithActions({
values: node.process.name,
fieldName: 'process.name',
idPrefix: `uncommon-process-table-${node._id}-processName`,
}),
},
{
align: 'right',
name: i18n.NUMBER_OF_HOSTS,
truncateText: false,
mobileOptions: { show: true },
render: ({ node }) => <>{node.hosts != null ? node.hosts.length : getEmptyValue()}</>,
width: '8%',
},
{
align: 'right',
name: i18n.NUMBER_OF_INSTANCES,
truncateText: false,
mobileOptions: { show: true },
render: ({ node }) => defaultToEmptyTag(node.instances),
width: '8%',
},
{
name: i18n.HOSTS,
truncateText: false,
mobileOptions: { show: true },
width: '25%',
render: ({ node }) =>
getRowItemsWithActions({
values: getHostNames(node.hosts),
fieldName: 'host.name',
idPrefix: `uncommon-process-table-${node._id}-processHost`,
render: (item) => <HostDetailsLink hostName={item} />,
}),
},
{
name: i18n.LAST_COMMAND,
truncateText: false,
mobileOptions: { show: true },
width: '25%',
render: ({ node }) =>
getRowItemsWithActions({
values: node.process != null ? node.process.args : null,
fieldName: 'process.args',
idPrefix: `uncommon-process-table-${node._id}-processArgs`,
displayCount: 1,
}),
},
{
name: i18n.LAST_USER,
truncateText: false,
mobileOptions: { show: true },
render: ({ node }) =>
getRowItemsWithActions({
values: node.user != null ? node.user.name : null,
fieldName: 'user.name',
idPrefix: `uncommon-process-table-${node._id}-processUser`,
}),
},
];

export const getUncommonColumnsCurated = (pageType: HostsType): UncommonProcessTableColumns => {
const columns: UncommonProcessTableColumns = getUncommonColumns();

if (pageType === HostsType.details) {
const columnsToRemove = new Set([i18n.HOSTS, i18n.NUMBER_OF_HOSTS]);
return columns.filter(
(column) => typeof column.name === 'string' && !columnsToRemove.has(column.name)
);
}

return columns;
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ import { hostsModel } from '../../store';
import { getEmptyValue } from '../../../../common/components/empty_value';
import { useMountAppended } from '../../../../common/utils/use_mount_appended';

import { getArgs, UncommonProcessTable, getUncommonColumnsCurated } from '.';
import { UncommonProcessTable } from '.';
import { mockData } from './mock';
import { HostsType } from '../../store/model';
import * as i18n from './translations';

jest.mock('../../../../common/lib/kibana');

Expand Down Expand Up @@ -151,55 +149,4 @@ describe('Uncommon Process Table Component', () => {
);
});
});

describe('#getArgs', () => {
test('it works with string array', () => {
const args = ['1', '2', '3'];
expect(getArgs(args)).toEqual('1 2 3');
});

test('it returns null if empty array', () => {
const args: string[] = [];
expect(getArgs(args)).toEqual(null);
});

test('it returns null if given null', () => {
expect(getArgs(null)).toEqual(null);
});

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

describe('#getUncommonColumnsCurated', () => {
test('on hosts page, we expect to get all columns', () => {
expect(getUncommonColumnsCurated(HostsType.page).length).toEqual(6);
});

test('on host details page, we expect to remove two columns', () => {
const columns = getUncommonColumnsCurated(HostsType.details);
expect(columns.length).toEqual(4);
});

test('on host page, we should have hosts', () => {
const columns = getUncommonColumnsCurated(HostsType.page);
expect(columns.some((col) => col.name === i18n.HOSTS)).toEqual(true);
});

test('on host page, we should have number of hosts', () => {
const columns = getUncommonColumnsCurated(HostsType.page);
expect(columns.some((col) => col.name === i18n.NUMBER_OF_HOSTS)).toEqual(true);
});

test('on host details page, we should not have hosts', () => {
const columns = getUncommonColumnsCurated(HostsType.details);
expect(columns.some((col) => col.name === i18n.HOSTS)).toEqual(false);
});

test('on host details page, we should not have number of hosts', () => {
const columns = getUncommonColumnsCurated(HostsType.details);
expect(columns.some((col) => col.name === i18n.NUMBER_OF_HOSTS)).toEqual(false);
});
});
});
Loading

0 comments on commit abfd30d

Please sign in to comment.