[SecuritySolution] Comma-separate array values in the alerts table (#133297)

* fix: comma-separate array values

* test: update snapshots

* fix: make sure that individual values are still draggable

* chore: reintroduce `parseValue`

We currently don't know exactly if it's still required or not. Hence we're reintroducing it and will revisit its usage in a future PR.

* chore: preserve `value`'s value

* chore: remove `parseValue`

`parseValue` was introduced in de6958d96f when the type of `value` included `object`. The type has been narrowed since then and does not include `object` anymore. Hence we are removing this runtime check.

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Jan Monschke 2022-06-06 14:46:51 +02:00 committed by GitHub
parent e7a1cef66f
commit 944e860779
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 94 additions and 20 deletions

View file

@ -640,6 +640,7 @@ export const mockTimelineData: TimelineItem[] = [
{ field: '@timestamp', value: ['2019-03-13T05:42:11.815Z'] },
{ field: 'event.category', value: ['audit-rule'] },
{ field: 'host.name', value: ['zeek-sanfran'] },
{ field: 'process.args', value: ['gpgconf', '--list-dirs', 'agent-socket'] },
],
ecs: {
_id: '20',

View file

@ -230,5 +230,42 @@ describe('plain_column_renderer', () => {
expect(wrapper.find('[data-test-subj="draggableWrapperDiv"]').first().exists()).toBe(true);
});
test('should join multiple values with a comma [not draggable]', () => {
const data = mockTimelineData[19].data;
const column = plainColumnRenderer.renderColumn({
columnName: 'process.args',
eventId: _id,
values: getValues('process.args', data),
field: defaultHeaders.find((h) => h.id === 'message')!,
timelineId: 'test',
isDraggable: false,
});
const wrapper = mount(
<TestProviders>
<span>{column}</span>
</TestProviders>
);
const values = getValues('process.args', data);
expect(wrapper.text()).toEqual(values?.join(', '));
});
test('should NOT join multiple values with a comma [draggable]', () => {
const data = mockTimelineData[19].data;
const column = plainColumnRenderer.renderColumn({
columnName: 'process.args',
eventId: _id,
values: getValues('process.args', data),
field: defaultHeaders.find((h) => h.id === 'message')!,
timelineId: 'test',
isDraggable: true,
});
const wrapper = mount(
<TestProviders>
<span>{column}</span>
</TestProviders>
);
expect(wrapper.find('[data-test-subj="draggableWrapperDiv"]').first().exists()).toBe(true);
});
});
});

View file

@ -14,7 +14,6 @@ import { TimelineNonEcsData } from '../../../../../../common/search_strategy/tim
import { getEmptyTagValue } from '../../../../../common/components/empty_value';
import { ColumnRenderer } from './column_renderer';
import { FormattedFieldValue } from './formatted_field';
import { parseValue } from './parse_value';
export const dataExistsAtColumn = (columnName: string, data: TimelineNonEcsData[]): boolean =>
data.findIndex((item) => item.field === columnName) !== -1;
@ -43,23 +42,60 @@ export const plainColumnRenderer: ColumnRenderer = {
truncate?: boolean;
values: string[] | undefined | null;
linkValues?: string[] | null | undefined;
}) =>
values != null
? values.map((value, i) => (
<FormattedFieldValue
asPlainText={asPlainText}
contextId={`plain-column-renderer-formatted-field-value-${timelineId}`}
eventId={eventId}
fieldFormat={field.format ?? ''}
fieldName={columnName}
isAggregatable={field.aggregatable ?? false}
fieldType={field.type ?? ''}
isDraggable={isDraggable}
key={`plain-column-renderer-formatted-field-value-${timelineId}-${columnName}-${eventId}-${field.id}-${value}-${i}`}
linkValue={head(linkValues)}
truncate={truncate}
value={parseValue(value)}
/>
))
: getEmptyTagValue(),
}) => {
if (!Array.isArray(values) || values.length === 0) {
return getEmptyTagValue();
}
// Draggable columns should render individual fields to give the user
// fine-grained control over the individual values
if (isDraggable) {
return values.map((value, i) => (
<FormattedFieldValue
asPlainText={asPlainText}
contextId={`plain-column-renderer-formatted-field-value-${timelineId}`}
eventId={eventId}
fieldFormat={field.format ?? ''}
fieldName={columnName}
isAggregatable={field.aggregatable ?? false}
fieldType={field.type ?? ''}
isDraggable={isDraggable}
key={`plain-column-renderer-formatted-field-value-${timelineId}-${columnName}-${eventId}-${field.id}-${value}-${i}`}
linkValue={head(linkValues)}
truncate={truncate}
value={value}
/>
));
} else {
// In case the column isn't draggable, fields are joined
// to give users a faster overview of all values.
// (note: the filter-related hover actions still produce individual filters for each value)
return (
<FormattedFieldValue
asPlainText={asPlainText}
contextId={`plain-column-renderer-formatted-field-value-${timelineId}`}
eventId={eventId}
fieldFormat={field.format ?? ''}
fieldName={columnName}
isAggregatable={field.aggregatable ?? false}
fieldType={field.type ?? ''}
isDraggable={isDraggable}
key={`plain-column-renderer-formatted-field-value-${timelineId}-${columnName}-${eventId}-${field.id}`}
linkValue={head(linkValues)}
truncate={truncate}
value={joinValues(values)}
/>
);
}
},
};
function joinValues(values: string[] | undefined | null): string | undefined | null {
if (Array.isArray(values)) {
if (values.length > 0) {
return values.join(', ');
} else {
return values[0];
}
}
return values;
}