Skip to content

Commit

Permalink
[Security Solution] - Analyzer panel improvements (elastic#172397)
Browse files Browse the repository at this point in the history
## Summary

Some improvements to analyzer panels:
- Adding hover actions and replaced description lists with table (search
bar is now available in event details)
   - Address elastic#139122
- Fixed a bug when event count is too large it break into second line
   - Address elastic#129987
- Clean up jest console errors


![image](https://github.com/elastic/kibana/assets/18648970/84afdafc-1767-49e9-807c-ebaec260d1b1)

![image](https://github.com/elastic/kibana/assets/18648970/cb370b59-f460-4e1c-aac8-256cef6328d8)

![image](https://github.com/elastic/kibana/assets/18648970/b3d07776-a847-42ef-a6e4-6a659348de72)

### Checklist

- [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/main/packages/kbn-i18n/README.md)
- [x] [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
  • Loading branch information
christineweng authored Jan 17, 2024
1 parent c79a231 commit 0bb216f
Show file tree
Hide file tree
Showing 13 changed files with 352 additions and 478 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -390,9 +390,9 @@ export class Simulator {
/**
* The titles and descriptions (as text) from the node detail panel.
*/
public nodeDetailDescriptionListEntries(): Array<[string, string]> {
public nodeDetailEntries(): Array<[string, string]> {
/**
* The details of the selected node are shown in a description list. This returns the title elements of the description list.
* The details of the selected node are shown in a table. This returns the title elements of the node list.
*/
const titles = this.domNodes('[data-test-subj="resolver:node-detail:entry-title"]');
/**
Expand Down
166 changes: 27 additions & 139 deletions x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,17 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and
* These are the details we expect to see in the node detail view when the origin is selected.
*/
const originEventDetailEntries: Array<[string, string]> = [
['@timestamp', 'Sep 23, 2020 @ 08:25:32.316'],
['process.executable', 'executable'],
['process.pid', '0'],
['process.entity_id', 'origin'],
['user.name', 'user.name'],
['user.domain', 'user.domain'],
['process.parent.pid', '0'],
['process.hash.md5', 'hash.md5'],
['process.args', 'args0'],
['process.args', 'args1'],
['process.args', 'args2'],
['Field@timestamp', 'ValueSep 23, 2020 @ 08:25:32.316'],
['Fieldprocess.executable', 'Valueexecutable'],
['Fieldprocess.pid', 'Value0'],
['Fieldprocess.entity_id', 'Valueorigin'],
['Fielduser.name', 'Valueuser.name'],
['Fielduser.domain', 'Valueuser.domain'],
['Fieldprocess.parent.pid', 'Value0'],
['Fieldprocess.hash.md5', 'Valuehash.md5'],
['Fieldprocess.args', 'Valueargs0'],
['Fieldprocess.args', 'Valueargs1'],
['Fieldprocess.args', 'Valueargs2'],
];

beforeEach(() => {
Expand Down Expand Up @@ -100,7 +100,7 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and
return {
title: titleWrapper.exists() ? titleWrapper.text() : null,
titleIcon: titleIconWrapper.exists() ? titleIconWrapper.text() : null,
detailEntries: simulator().nodeDetailDescriptionListEntries(),
detailEntries: simulator().nodeDetailEntries(),
};
})
).toYieldEqualTo({
Expand All @@ -122,51 +122,6 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and
wordBreaks: 2,
});
});

/**
* These tests use a statically defined map of fields and expected values. The test finds the `dt` for each field and then finds the related `dd`s. From there it finds a special 'hover area' (via `data-test-subj`) and simulates a `mouseenter` on it. This is because the feature work by adding event listeners to `div`s. There is no way for the user to know that the `div`s are interactable.
* Finally the test clicks a button and checks that the clipboard was written to.
*/
describe.each([...originEventDetailEntries])(
'when the user hovers over the description for the field (%p) with their mouse',
(fieldTitleText, value) => {
// If there are multiple values for a field, i.e. an array, this is the index for the value we are testing.
const entryIndex = originEventDetailEntries
.filter(([fieldName]) => fieldName === fieldTitleText)
.findIndex(([_, fieldValue]) => fieldValue === value);
beforeEach(async () => {
const dt = await simulator().resolveWrapper(() => {
return simulator()
.testSubject('resolver:node-detail:entry-title')
.filterWhere((title) => title.text() === fieldTitleText)
.at(entryIndex);
});

expect(dt).toHaveLength(1);

const copyableFieldHoverArea = simulator()
.descriptionDetails(dt!)
// The copyable field popup does not use a button as a trigger. It is instead triggered by mouse interaction with this `div`.
.find(`[data-test-subj="resolver:panel:copyable-field-hover-area"]`)
.filterWhere(Simulator.isDOM);

expect(copyableFieldHoverArea).toHaveLength(1);
copyableFieldHoverArea?.simulate('mouseenter');
});
describe('and when they click the copy-to-clipboard button', () => {
beforeEach(async () => {
const copyButton = await simulator().resolve('resolver:panel:clipboard');
expect(copyButton).toHaveLength(1);
copyButton?.simulate('click');
simulator().confirmTextWrittenToClipboard();
});
it(`should write ${value} to the clipboard`, async () => {
await expect(simulator().map(() => simulator().clipboardText)).toYieldEqualTo(value);
});
});
}
);
});

const queryStringWithFirstChildSelected = urlSearch(resolverComponentInstanceID, {
Expand All @@ -181,20 +136,18 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and
});
});
it('should show the node details for the first child', async () => {
await expect(
simulator().map(() => simulator().nodeDetailDescriptionListEntries())
).toYieldEqualTo([
['@timestamp', 'Sep 23, 2020 @ 08:25:32.317'],
['process.executable', 'executable'],
['process.pid', '1'],
['process.entity_id', 'firstChild'],
['user.name', 'user.name'],
['user.domain', 'user.domain'],
['process.parent.pid', '0'],
['process.hash.md5', 'hash.md5'],
['process.args', 'args0'],
['process.args', 'args1'],
['process.args', 'args2'],
await expect(simulator().map(() => simulator().nodeDetailEntries())).toYieldEqualTo([
['Field@timestamp', 'ValueSep 23, 2020 @ 08:25:32.317'],
['Fieldprocess.executable', 'Valueexecutable'],
['Fieldprocess.pid', 'Value1'],
['Fieldprocess.entity_id', 'ValuefirstChild'],
['Fielduser.name', 'Valueuser.name'],
['Fielduser.domain', 'Valueuser.domain'],
['Fieldprocess.parent.pid', 'Value0'],
['Fieldprocess.hash.md5', 'Valuehash.md5'],
['Fieldprocess.args', 'Valueargs0'],
['Fieldprocess.args', 'Valueargs1'],
['Fieldprocess.args', 'Valueargs2'],
]);
});
});
Expand All @@ -218,37 +171,6 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and
).toYieldEqualTo({ titleCount: 3, iconCount: 3 });
});

describe('when the user hovers over the timestamp for "c.ext" with their mouse', () => {
beforeEach(async () => {
const cExtHoverArea = await simulator().resolveWrapper(async () => {
const nodeLinkTitles = await simulator().resolve('resolver:node-list:node-link:title');

expect(nodeLinkTitles).toHaveLength(3);

return (
nodeLinkTitles!
.filterWhere((linkTitle) => linkTitle.text() === 'c.ext')
// Find the parent `tr` and the find all hover areas in that TR. The test assumes that all cells in a row are associated.
.closest('tr')
// The copyable field popup does not use a button as a trigger. It is instead triggered by mouse interaction with this `div`.
.find('[data-test-subj="resolver:panel:copyable-field-hover-area"]')
.filterWhere(Simulator.isDOM)
);
});
cExtHoverArea?.simulate('mouseenter');
});
describe('and when the user clicks the copy-to-clipboard button', () => {
beforeEach(async () => {
(await simulator().resolve('resolver:panel:clipboard'))?.simulate('click');
simulator().confirmTextWrittenToClipboard();
});
const expected = 'Sep 23, 2020 @ 08:25:32.316';
it(`should write "${expected}" to the clipboard`, async () => {
await expect(simulator().map(() => simulator().clipboardText)).toYieldEqualTo(expected);
});
});
});

describe('when there is an item in the node list and its text has been clicked', () => {
beforeEach(async () => {
const nodeLinks = await simulator().resolve('resolver:node-list:node-link:title');
Expand All @@ -258,9 +180,9 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and
}
});
it('should show the details for the first node', async () => {
await expect(
simulator().map(() => simulator().nodeDetailDescriptionListEntries())
).toYieldEqualTo([...originEventDetailEntries]);
await expect(simulator().map(() => simulator().nodeDetailEntries())).toYieldEqualTo([
...originEventDetailEntries,
]);
});
it("should have the first node's ID in the query string", async () => {
await expect(simulator().map(() => simulator().historyLocationSearch)).toYieldEqualTo(
Expand Down Expand Up @@ -349,40 +271,6 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and
simulator().map(() => simulator().testSubject('resolver:panel:event-detail').length)
).toYieldEqualTo(1);
});
describe.each([['user.domain', 'user.domain']])(
'when the user hovers over the description for the field "%p"',
(fieldName, expectedValue) => {
beforeEach(async () => {
const fieldHoverArea = await simulator().resolveWrapper(async () => {
const dt = (
await simulator().resolve('resolver:panel:event-detail:event-field-title')
)?.filterWhere((title) => title.text() === fieldName);
return (
simulator()
.descriptionDetails(dt!)
// The copyable field popup does not use a button as a trigger. It is instead triggered by mouse interaction with this `div`.
.find(`[data-test-subj="resolver:panel:copyable-field-hover-area"]`)
.filterWhere(Simulator.isDOM)
);
});
expect(fieldHoverArea).toBeTruthy();
fieldHoverArea?.simulate('mouseenter');
});
describe('when the user clicks on the clipboard button', () => {
beforeEach(async () => {
const button = await simulator().resolve('resolver:panel:clipboard');
expect(button).toBeTruthy();
button?.simulate('click');
simulator().confirmTextWrittenToClipboard();
});
it(`should write ${expectedValue} to the clipboard`, async () => {
await expect(simulator().map(() => simulator().clipboardText)).toYieldEqualTo(
expectedValue
);
});
});
}
);
});
});
});
Expand Down

This file was deleted.

Loading

0 comments on commit 0bb216f

Please sign in to comment.