mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution] Fix: destination of the command link is a host details 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: https://github.com/elastic/kibana/issues/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 <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
c11d5345b8
commit
abfd30da75
5 changed files with 205 additions and 172 deletions
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
};
|
|
@ -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');
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,17 +8,13 @@
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import type { HostEcs } from '@kbn/securitysolution-ecs';
|
||||
import type { HostsUncommonProcessesEdges } from '../../../../../common/search_strategy';
|
||||
import { hostsActions, hostsModel, hostsSelectors } from '../../store';
|
||||
import { defaultToEmptyTag, getEmptyValue } from '../../../../common/components/empty_value';
|
||||
import { HostDetailsLink } from '../../../../common/components/links';
|
||||
import type { Columns, ItemsPerRow } from '../../../components/paginated_table';
|
||||
import type { ItemsPerRow } from '../../../components/paginated_table';
|
||||
import { PaginatedTable } from '../../../components/paginated_table';
|
||||
import * as i18n from './translations';
|
||||
import { getRowItemsWithActions } from '../../../../common/components/tables/helpers';
|
||||
import { HostsType } from '../../store/model';
|
||||
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
|
||||
import { getUncommonColumnsCurated } from './columns';
|
||||
|
||||
const tableType = hostsModel.HostsTableType.uncommonProcesses;
|
||||
interface UncommonProcessTableProps {
|
||||
|
@ -34,15 +30,6 @@ interface UncommonProcessTableProps {
|
|||
type: hostsModel.HostsType;
|
||||
}
|
||||
|
||||
export type UncommonProcessTableColumns = [
|
||||
Columns<HostsUncommonProcessesEdges>,
|
||||
Columns<HostsUncommonProcessesEdges>,
|
||||
Columns<HostsUncommonProcessesEdges>,
|
||||
Columns<HostsUncommonProcessesEdges>,
|
||||
Columns<HostsUncommonProcessesEdges>,
|
||||
Columns<HostsUncommonProcessesEdges>
|
||||
];
|
||||
|
||||
const rowItems: ItemsPerRow[] = [
|
||||
{
|
||||
text: i18n.ROWS_5,
|
||||
|
@ -54,14 +41,6 @@ const rowItems: ItemsPerRow[] = [
|
|||
},
|
||||
];
|
||||
|
||||
export const getArgs = (args: string[] | null | undefined): string | null => {
|
||||
if (args != null && args.length !== 0) {
|
||||
return args.join(' ');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const UncommonProcessTableComponent = React.memo<UncommonProcessTableProps>(
|
||||
({
|
||||
data,
|
||||
|
@ -140,97 +119,3 @@ UncommonProcessTableComponent.displayName = 'UncommonProcessTableComponent';
|
|||
export const UncommonProcessTable = React.memo(UncommonProcessTableComponent);
|
||||
|
||||
UncommonProcessTable.displayName = 'UncommonProcessTable';
|
||||
|
||||
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`,
|
||||
render: (item) => <HostDetailsLink hostName={item} />,
|
||||
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 getHostNames = (hosts: HostEcs[]): string[] => {
|
||||
if (hosts != null) {
|
||||
return hosts
|
||||
.filter((host) => host.name != null && host.name[0] != null)
|
||||
.map((host) => (host.name != null && host.name[0] != null ? host.name[0] : ''));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const getUncommonColumnsCurated = (pageType: HostsType): UncommonProcessTableColumns => {
|
||||
const columns: UncommonProcessTableColumns = getUncommonColumns();
|
||||
if (pageType === HostsType.details) {
|
||||
return [i18n.HOSTS, i18n.NUMBER_OF_HOSTS].reduce<UncommonProcessTableColumns>((acc, name) => {
|
||||
acc.splice(
|
||||
acc.findIndex((column) => column.name === name),
|
||||
1
|
||||
);
|
||||
return acc;
|
||||
}, columns);
|
||||
} else {
|
||||
return columns;
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue