[Lists][Exceptions] - Updates exception list item comments structure (#68864)

### Summary

This is part of a series of upcoming changes to the exception list item structure. This PR focuses solely on updating exception_item.comment. The hope is to keep these PRs relatively small.

- Updates exception_item.comment structure which was previously a string to exception_item.comments which is an array of { comment: string; created_by: string; created_at: string; }
- Adds a few unit tests server side
- Fixes some minor misspellings
- Updates ExceptionViewer component in the UI to account for new structure
This commit is contained in:
Yara Tercero 2020-06-11 11:41:31 -04:00 committed by GitHub
parent 5f3c3c0354
commit da5aa03583
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 866 additions and 90 deletions

View file

@ -149,7 +149,7 @@ And you can attach exception list items like so:
"malware",
"os:linux"
],
"comment": [],
"comments": [],
"created_at": "2020-05-28T19:17:21.099Z",
"created_by": "yo",
"description": "This is a sample endpoint type exception",

View file

@ -31,6 +31,7 @@ export const VALUE_2 = '255.255.255';
export const NAMESPACE_TYPE = 'single';
// Exception List specific
export const ID = 'uuid_here';
export const ENDPOINT_TYPE = 'endpoint';
export const ENTRIES = [
{ field: 'some.field', match: 'some value', match_any: undefined, operator: 'included' },
@ -38,4 +39,4 @@ export const ENTRIES = [
export const ITEM_TYPE = 'simple';
export const _TAGS = [];
export const TAGS = [];
export const COMMENT = [];
export const COMMENTS = [];

View file

@ -86,12 +86,6 @@ export type ExceptionListItemType = t.TypeOf<typeof exceptionListItemType>;
export const list_type = t.keyof({ item: null, list: null });
export type ListType = t.TypeOf<typeof list_type>;
// TODO: Investigate what the deep structure of a comment is really going to be and then change this to use that deep structure with a default array
export const comment = DefaultStringArray;
export type Comment = t.TypeOf<typeof comment>;
export const commentOrUndefined = t.union([comment, t.undefined]);
export type CommentOrUndefined = t.TypeOf<typeof commentOrUndefined>;
export const item_id = NonEmptyString;
export type ItemId = t.TypeOf<typeof item_id>;
export const itemIdOrUndefined = t.union([item_id, t.undefined]);

View file

@ -5,7 +5,7 @@
*/
import {
COMMENT,
COMMENTS,
DESCRIPTION,
ENTRIES,
ITEM_TYPE,
@ -21,7 +21,7 @@ import { CreateExceptionListItemSchema } from './create_exception_list_item_sche
export const getCreateExceptionListItemSchemaMock = (): CreateExceptionListItemSchema => ({
_tags: _TAGS,
comment: COMMENT,
comments: COMMENTS,
description: DESCRIPTION,
entries: ENTRIES,
item_id: undefined,

View file

@ -0,0 +1,189 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { left } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps';
import {
CreateExceptionListItemSchema,
createExceptionListItemSchema,
} from './create_exception_list_item_schema';
import { getCreateExceptionListItemSchemaMock } from './create_exception_list_item_schema.mock';
describe('create_exception_list_schema', () => {
test('it should validate a typical exception list item request', () => {
const payload = getCreateExceptionListItemSchemaMock();
const outputPayload = getCreateExceptionListItemSchemaMock();
const decoded = createExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
outputPayload.item_id = (message.schema as CreateExceptionListItemSchema).item_id;
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(outputPayload);
});
test('it should not accept an undefined for "description"', () => {
const payload = getCreateExceptionListItemSchemaMock();
delete payload.description;
const decoded = createExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "description"',
]);
expect(message.schema).toEqual({});
});
test('it should not accept an undefined for "name"', () => {
const payload = getCreateExceptionListItemSchemaMock();
delete payload.name;
const decoded = createExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "name"',
]);
expect(message.schema).toEqual({});
});
test('it should not accept an undefined for "type"', () => {
const payload = getCreateExceptionListItemSchemaMock();
delete payload.type;
const decoded = createExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "type"',
]);
expect(message.schema).toEqual({});
});
test('it should not accept an undefined for "list_id"', () => {
const inputPayload = getCreateExceptionListItemSchemaMock();
delete inputPayload.list_id;
const decoded = createExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "list_id"',
]);
expect(message.schema).toEqual({});
});
test('it should accept an undefined for "meta" but strip it out', () => {
const payload = getCreateExceptionListItemSchemaMock();
const outputPayload = getCreateExceptionListItemSchemaMock();
delete payload.meta;
const decoded = createExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
delete outputPayload.meta;
outputPayload.item_id = (message.schema as CreateExceptionListItemSchema).item_id;
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(outputPayload);
});
test('it should accept an undefined for "comments" but return an array', () => {
const inputPayload = getCreateExceptionListItemSchemaMock();
const outputPayload = getCreateExceptionListItemSchemaMock();
delete inputPayload.comments;
outputPayload.comments = [];
const decoded = createExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
outputPayload.item_id = (message.schema as CreateExceptionListItemSchema).item_id;
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(outputPayload);
});
test('it should accept an undefined for "entries" but return an array', () => {
const inputPayload = getCreateExceptionListItemSchemaMock();
const outputPayload = getCreateExceptionListItemSchemaMock();
delete inputPayload.entries;
outputPayload.entries = [];
const decoded = createExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
outputPayload.item_id = (message.schema as CreateExceptionListItemSchema).item_id;
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(outputPayload);
});
test('it should accept an undefined for "namespace_type" but return enum "single"', () => {
const inputPayload = getCreateExceptionListItemSchemaMock();
const outputPayload = getCreateExceptionListItemSchemaMock();
delete inputPayload.namespace_type;
outputPayload.namespace_type = 'single';
const decoded = createExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
outputPayload.item_id = (message.schema as CreateExceptionListItemSchema).item_id;
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(outputPayload);
});
test('it should accept an undefined for "tags" but return an array', () => {
const inputPayload = getCreateExceptionListItemSchemaMock();
const outputPayload = getCreateExceptionListItemSchemaMock();
delete inputPayload.tags;
outputPayload.tags = [];
const decoded = createExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
outputPayload.item_id = (message.schema as CreateExceptionListItemSchema).item_id;
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(outputPayload);
});
test('it should accept an undefined for "_tags" but return an array', () => {
const inputPayload = getCreateExceptionListItemSchemaMock();
const outputPayload = getCreateExceptionListItemSchemaMock();
delete inputPayload._tags;
outputPayload._tags = [];
const decoded = createExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
outputPayload.item_id = (message.schema as CreateExceptionListItemSchema).item_id;
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(outputPayload);
});
test('it should accept an undefined for "item_id" and auto generate a uuid', () => {
const inputPayload = getCreateExceptionListItemSchemaMock();
delete inputPayload.item_id;
const decoded = createExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect((message.schema as CreateExceptionListItemSchema).item_id).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i
);
});
test('it should accept an undefined for "item_id" and generate a correct body not counting the uuid', () => {
const inputPayload = getCreateExceptionListItemSchemaMock();
delete inputPayload.item_id;
const decoded = createExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
delete (message.schema as CreateExceptionListItemSchema).item_id;
expect(message.schema).toEqual(inputPayload);
});
test('it should not allow an extra key to be sent in', () => {
const payload: CreateExceptionListItemSchema & {
extraKey?: string;
} = getCreateExceptionListItemSchemaMock();
payload.extraKey = 'some new value';
const decoded = createExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']);
expect(message.schema).toEqual({});
});
});

View file

@ -14,7 +14,6 @@ import {
Tags,
_Tags,
_tags,
comment,
description,
exceptionListItemType,
list_id,
@ -24,7 +23,7 @@ import {
tags,
} from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { DefaultEntryArray } from '../types';
import { CommentsPartialArray, DefaultCommentsPartialArray, DefaultEntryArray } from '../types';
import { EntriesArray } from '../types/entries';
import { DefaultUuid } from '../../siem_common_deps';
@ -40,7 +39,7 @@ export const createExceptionListItemSchema = t.intersection([
t.exact(
t.partial({
_tags, // defaults to empty array if not set during decode
comment, // defaults to empty array if not set during decode
comments: DefaultCommentsPartialArray, // defaults to empty array if not set during decode
entries: DefaultEntryArray, // defaults to empty array if not set during decode
item_id: DefaultUuid, // defaults to GUID (uuid v4) if not set during decode
meta, // defaults to undefined if not set during decode
@ -61,9 +60,10 @@ export type CreateExceptionListItemSchema = RequiredKeepUndefined<
export type CreateExceptionListItemSchemaDecoded = Identity<
Omit<
CreateExceptionListItemSchema,
'_tags' | 'tags' | 'item_id' | 'entries' | 'namespace_type'
'_tags' | 'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments'
> & {
_tags: _Tags;
comments: CommentsPartialArray;
tags: Tags;
item_id: ItemId;
entries: EntriesArray;

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import {
COMMENTS,
DESCRIPTION,
ENTRIES,
ID,
ITEM_TYPE,
LIST_ITEM_ID,
META,
NAME,
NAMESPACE_TYPE,
TAGS,
_TAGS,
} from '../../constants.mock';
import { UpdateExceptionListItemSchema } from './update_exception_list_item_schema';
export const getUpdateExceptionListItemSchemaMock = (): UpdateExceptionListItemSchema => ({
_tags: _TAGS,
comments: COMMENTS,
description: DESCRIPTION,
entries: ENTRIES,
id: ID,
item_id: LIST_ITEM_ID,
meta: META,
name: NAME,
namespace_type: NAMESPACE_TYPE,
tags: TAGS,
type: ITEM_TYPE,
});

View file

@ -0,0 +1,183 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { left } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps';
import {
UpdateExceptionListItemSchema,
updateExceptionListItemSchema,
} from './update_exception_list_item_schema';
import { getUpdateExceptionListItemSchemaMock } from './update_exception_list_item_schema.mock';
describe('update_exception_list_item_schema', () => {
test('it should validate a typical exception list item request', () => {
const payload = getUpdateExceptionListItemSchemaMock();
const decoded = updateExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});
test('it should not accept an undefined for "description"', () => {
const payload = getUpdateExceptionListItemSchemaMock();
delete payload.description;
const decoded = updateExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "description"',
]);
expect(message.schema).toEqual({});
});
test('it should not accept an undefined for "name"', () => {
const payload = getUpdateExceptionListItemSchemaMock();
delete payload.name;
const decoded = updateExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "name"',
]);
expect(message.schema).toEqual({});
});
test('it should not accept an undefined for "type"', () => {
const payload = getUpdateExceptionListItemSchemaMock();
delete payload.type;
const decoded = updateExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "type"',
]);
expect(message.schema).toEqual({});
});
test('it should not accept a value for "list_id"', () => {
const payload: UpdateExceptionListItemSchema & {
list_id?: string;
} = getUpdateExceptionListItemSchemaMock();
payload.list_id = 'some new list_id';
const decoded = updateExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual(['invalid keys "list_id"']);
expect(message.schema).toEqual({});
});
test('it should accept an undefined for "meta" but strip it out', () => {
const payload = getUpdateExceptionListItemSchemaMock();
const outputPayload = getUpdateExceptionListItemSchemaMock();
delete payload.meta;
const decoded = updateExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
delete outputPayload.meta;
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(outputPayload);
});
test('it should accept an undefined for "comments" but return an array', () => {
const inputPayload = getUpdateExceptionListItemSchemaMock();
const outputPayload = getUpdateExceptionListItemSchemaMock();
delete inputPayload.comments;
outputPayload.comments = [];
const decoded = updateExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(outputPayload);
});
test('it should accept an undefined for "entries" but return an array', () => {
const inputPayload = getUpdateExceptionListItemSchemaMock();
const outputPayload = getUpdateExceptionListItemSchemaMock();
delete inputPayload.entries;
outputPayload.entries = [];
const decoded = updateExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(outputPayload);
});
test('it should accept an undefined for "namespace_type" but return enum "single"', () => {
const inputPayload = getUpdateExceptionListItemSchemaMock();
const outputPayload = getUpdateExceptionListItemSchemaMock();
delete inputPayload.namespace_type;
outputPayload.namespace_type = 'single';
const decoded = updateExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(outputPayload);
});
test('it should accept an undefined for "tags" but return an array', () => {
const inputPayload = getUpdateExceptionListItemSchemaMock();
const outputPayload = getUpdateExceptionListItemSchemaMock();
delete inputPayload.tags;
outputPayload.tags = [];
const decoded = updateExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(outputPayload);
});
test('it should accept an undefined for "_tags" but return an array', () => {
const inputPayload = getUpdateExceptionListItemSchemaMock();
const outputPayload = getUpdateExceptionListItemSchemaMock();
delete inputPayload._tags;
outputPayload._tags = [];
const decoded = updateExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(outputPayload);
});
// TODO: Is it expected behavior for it not to auto-generate a uui or throw
// error if item_id is not passed in?
xtest('it should accept an undefined for "item_id" and auto generate a uuid', () => {
const inputPayload = getUpdateExceptionListItemSchemaMock();
delete inputPayload.item_id;
const decoded = updateExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect((message.schema as UpdateExceptionListItemSchema).item_id).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i
);
});
test('it should accept an undefined for "item_id" and generate a correct body not counting the uuid', () => {
const inputPayload = getUpdateExceptionListItemSchemaMock();
delete inputPayload.item_id;
const decoded = updateExceptionListItemSchema.decode(inputPayload);
const checked = exactCheck(inputPayload, decoded);
const message = pipe(checked, foldLeftRight);
delete (message.schema as UpdateExceptionListItemSchema).item_id;
expect(message.schema).toEqual(inputPayload);
});
test('it should not allow an extra key to be sent in', () => {
const payload: UpdateExceptionListItemSchema & {
extraKey?: string;
} = getUpdateExceptionListItemSchemaMock();
payload.extraKey = 'some new value';
const decoded = updateExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']);
expect(message.schema).toEqual({});
});
});

View file

@ -13,7 +13,6 @@ import {
Tags,
_Tags,
_tags,
comment,
description,
exceptionListItemType,
id,
@ -23,8 +22,12 @@ import {
tags,
} from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { DefaultEntryArray } from '../types';
import { EntriesArray } from '../types/entries';
import {
CommentsPartialArray,
DefaultCommentsPartialArray,
DefaultEntryArray,
EntriesArray,
} from '../types';
export const updateExceptionListItemSchema = t.intersection([
t.exact(
@ -37,7 +40,7 @@ export const updateExceptionListItemSchema = t.intersection([
t.exact(
t.partial({
_tags, // defaults to empty array if not set during decode
comment, // defaults to empty array if not set during decode
comments: DefaultCommentsPartialArray, // defaults to empty array if not set during decode
entries: DefaultEntryArray, // defaults to empty array if not set during decode
id, // defaults to undefined if not set during decode
item_id: t.union([t.string, t.undefined]),
@ -57,8 +60,12 @@ export type UpdateExceptionListItemSchema = RequiredKeepUndefined<
// This type is used after a decode since some things are defaults after a decode.
export type UpdateExceptionListItemSchemaDecoded = Identity<
Omit<UpdateExceptionListItemSchema, '_tags' | 'tags' | 'entries' | 'namespace_type'> & {
Omit<
UpdateExceptionListItemSchema,
'_tags' | 'tags' | 'entries' | 'namespace_type' | 'comments'
> & {
_tags: _Tags;
comments: CommentsPartialArray;
tags: Tags;
entries: EntriesArray;
namespace_type: NamespaceType;

View file

@ -8,7 +8,7 @@ import { ExceptionListItemSchema } from './exception_list_item_schema';
export const getExceptionListItemSchemaMock = (): ExceptionListItemSchema => ({
_tags: ['endpoint', 'process', 'malware', 'os:linux'],
comment: [],
comments: [],
created_at: '2020-04-23T00:19:13.289Z',
created_by: 'user_name',
description: 'This is a sample endpoint type exception',

View file

@ -0,0 +1,230 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { left } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps';
import { getExceptionListItemSchemaMock } from './exception_list_item_schema.mock';
import { ExceptionListItemSchema, exceptionListItemSchema } from './exception_list_item_schema';
describe('exception_list_item_schema', () => {
test('it should validate a typical exception list item response', () => {
const payload = getExceptionListItemSchemaMock();
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});
test('it should NOT accept an undefined for "id"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.id;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual(['Invalid value "undefined" supplied to "id"']);
expect(message.schema).toEqual({});
});
test('it should NOT accept an undefined for "list_id"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.list_id;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "list_id"',
]);
expect(message.schema).toEqual({});
});
test('it should NOT accept an undefined for "item_id"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.item_id;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "item_id"',
]);
expect(message.schema).toEqual({});
});
test('it should NOT accept an undefined for "comments"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.comments;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "comments"',
]);
expect(message.schema).toEqual({});
});
test('it should NOT accept an undefined for "entries"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.entries;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "entries"',
]);
expect(message.schema).toEqual({});
});
test('it should NOT accept an undefined for "name"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.name;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "name"',
]);
expect(message.schema).toEqual({});
});
// TODO: Should this throw an error? "namespace_type" gets auto-populated
// with default "single", is that desired behavior?
xtest('it should NOT accept an undefined for "namespace_type"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.namespace_type;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "namespace_type"',
]);
expect(message.schema).toEqual({});
});
test('it should NOT accept an undefined for "description"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.description;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "description"',
]);
expect(message.schema).toEqual({});
});
test('it should accept an undefined for "meta"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.meta;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});
test('it should NOT accept an undefined for "created_at"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.created_at;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "created_at"',
]);
expect(message.schema).toEqual({});
});
test('it should NOT accept an undefined for "created_by"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.created_by;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "created_by"',
]);
expect(message.schema).toEqual({});
});
test('it should NOT accept an undefined for "tie_breaker_id"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.tie_breaker_id;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "tie_breaker_id"',
]);
expect(message.schema).toEqual({});
});
test('it should NOT accept an undefined for "type"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.type;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "type"',
]);
expect(message.schema).toEqual({});
});
test('it should NOT accept an undefined for "updated_at"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.updated_at;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "updated_at"',
]);
expect(message.schema).toEqual({});
});
test('it should NOT accept an undefined for "updated_by"', () => {
const payload = getExceptionListItemSchemaMock();
delete payload.updated_by;
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "updated_by"',
]);
expect(message.schema).toEqual({});
});
test('it should not allow an extra key to be sent in', () => {
const payload: ExceptionListItemSchema & {
extraKey?: string;
} = getExceptionListItemSchemaMock();
payload.extraKey = 'some new value';
const decoded = exceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']);
expect(message.schema).toEqual({});
});
});

View file

@ -10,7 +10,6 @@ import * as t from 'io-ts';
import {
_tags,
commentOrUndefined,
created_at,
created_by,
description,
@ -26,13 +25,13 @@ import {
updated_at,
updated_by,
} from '../common/schemas';
import { entriesArray } from '../types';
import { commentsArray, entriesArray } from '../types';
// TODO: Should we use a partial here to reflect that this can JSON serialize meta, comment as non existent?
export const exceptionListItemSchema = t.exact(
t.type({
_tags,
comment: commentOrUndefined,
comments: commentsArray,
created_at,
created_by,
description,

View file

@ -8,10 +8,9 @@
import * as t from 'io-ts';
import { entriesArrayOrUndefined } from '../types';
import { commentsArrayOrUndefined, entriesArrayOrUndefined } from '../types';
import {
_tags,
commentOrUndefined,
created_at,
created_by,
description,
@ -30,7 +29,7 @@ import {
export const exceptionListSoSchema = t.exact(
t.type({
_tags,
comment: commentOrUndefined,
comments: commentsArrayOrUndefined,
created_at,
created_by,
description,

View file

@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import * as t from 'io-ts';
export const comment = t.exact(
t.type({
comment: t.string,
created_at: t.string, // TODO: Make this into an ISO Date string check,
created_by: t.string,
})
);
export const commentsArray = t.array(comment);
export type CommentsArray = t.TypeOf<typeof commentsArray>;
export type Comment = t.TypeOf<typeof comment>;
export const commentsArrayOrUndefined = t.union([commentsArray, t.undefined]);
export type CommentsArrayOrUndefined = t.TypeOf<typeof commentsArrayOrUndefined>;
export const commentPartial = t.intersection([
t.exact(
t.type({
comment: t.string,
})
),
t.exact(
t.partial({
created_at: t.string, // TODO: Make this into an ISO Date string check,
created_by: t.string,
})
),
]);
export const commentsPartialArray = t.array(commentPartial);
export type CommentsPartialArray = t.TypeOf<typeof commentsPartialArray>;
export type CommentPartial = t.TypeOf<typeof commentPartial>;
export const commentsPartialArrayOrUndefined = t.union([commentsPartialArray, t.undefined]);
export type CommentsPartialArrayOrUndefined = t.TypeOf<typeof commentsPartialArrayOrUndefined>;

View file

@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import * as t from 'io-ts';
import { Either } from 'fp-ts/lib/Either';
import { CommentsArray, CommentsPartialArray, comment, commentPartial } from './comments';
export type DefaultCommentsArrayC = t.Type<CommentsArray, CommentsArray, unknown>;
export type DefaultCommentsPartialArrayC = t.Type<
CommentsPartialArray,
CommentsPartialArray,
unknown
>;
/**
* Types the DefaultCommentsArray as:
* - If null or undefined, then a default array of type entry will be set
*/
export const DefaultCommentsArray: DefaultCommentsArrayC = new t.Type<
CommentsArray,
CommentsArray,
unknown
>(
'DefaultCommentsArray',
t.array(comment).is,
(input): Either<t.Errors, CommentsArray> =>
input == null ? t.success([]) : t.array(comment).decode(input),
t.identity
);
/**
* Types the DefaultCommentsPartialArray as:
* - If null or undefined, then a default array of type entry will be set
*/
export const DefaultCommentsPartialArray: DefaultCommentsPartialArrayC = new t.Type<
CommentsPartialArray,
CommentsPartialArray,
unknown
>(
'DefaultCommentsPartialArray',
t.array(commentPartial).is,
(input): Either<t.Errors, CommentsPartialArray> =>
input == null ? t.success([]) : t.array(commentPartial).decode(input),
t.identity
);

View file

@ -3,5 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export * from './default_comments_array';
export * from './default_entries_array';
export * from './comments';
export * from './entries';

View file

@ -44,7 +44,7 @@ export const createExceptionListItemRoute = (router: IRouter): void => {
_tags,
tags,
meta,
comment,
comments,
description,
entries,
item_id: itemId,
@ -76,7 +76,7 @@ export const createExceptionListItemRoute = (router: IRouter): void => {
} else {
const createdList = await exceptionLists.createExceptionListItem({
_tags,
comment,
comments,
description,
entries,
itemId,

View file

@ -45,7 +45,7 @@ export const updateExceptionListItemRoute = (router: IRouter): void => {
meta,
type,
_tags,
comment,
comments,
entries,
item_id: itemId,
namespace_type: namespaceType,
@ -54,7 +54,7 @@ export const updateExceptionListItemRoute = (router: IRouter): void => {
const exceptionLists = getExceptionListClient(context);
const exceptionListItem = await exceptionLists.updateExceptionListItem({
_tags,
comment,
comments,
description,
entries,
id,

View file

@ -66,9 +66,18 @@ export const exceptionListMapping: SavedObjectsType['mappings'] = {
export const exceptionListItemMapping: SavedObjectsType['mappings'] = {
properties: {
comment: {
// TODO: investigate what the deep mapping structure of this really is
type: 'keyword',
comments: {
properties: {
comment: {
type: 'keyword',
},
created_at: {
type: 'keyword',
},
created_by: {
type: 'keyword',
},
},
},
entries: {
properties: {

View file

@ -5,7 +5,7 @@
"type": "simple",
"description": "This is a sample endpoint type exception that has no item_id so it creates a new id each time",
"name": "Sample Endpoint Exception List",
"comment": [],
"comments": [],
"entries": [
{
"field": "actingProcess.file.signer",

View file

@ -5,7 +5,7 @@
"type": "simple",
"description": "This is a sample detection type exception that has no item_id so it creates a new id each time",
"name": "Sample Detection Exception List Item",
"comment": [],
"comments": [{ "comment": "This is a short little comment." }],
"entries": [
{
"field": "host.name",

View file

@ -5,6 +5,14 @@
"type": "simple",
"description": "This is a sample change here this list",
"name": "Sample Endpoint Exception List update change",
"comments": [
{
"comment": "this was an old comment.",
"created_by": "lily",
"created_at": "2020-04-20T15:25:31.830Z"
},
{ "comment": "this is a newly added comment" }
],
"entries": [
{
"field": "event.category",

View file

@ -53,7 +53,7 @@ export const createExceptionList = async ({
const dateNow = new Date().toISOString();
const savedObject = await savedObjectsClient.create<ExceptionListSoSchema>(savedObjectType, {
_tags,
comment: undefined,
comments: undefined,
created_at: dateNow,
created_by: user,
description,

View file

@ -8,7 +8,7 @@ import { SavedObjectsClientContract } from 'kibana/server';
import uuid from 'uuid';
import {
CommentOrUndefined,
CommentsPartialArray,
Description,
EntriesArray,
ExceptionListItemSchema,
@ -23,11 +23,15 @@ import {
_Tags,
} from '../../../common/schemas';
import { getSavedObjectType, transformSavedObjectToExceptionListItem } from './utils';
import {
getSavedObjectType,
transformComments,
transformSavedObjectToExceptionListItem,
} from './utils';
interface CreateExceptionListItemOptions {
_tags: _Tags;
comment: CommentOrUndefined;
comments: CommentsPartialArray;
listId: ListId;
itemId: ItemId;
savedObjectsClient: SavedObjectsClientContract;
@ -44,7 +48,7 @@ interface CreateExceptionListItemOptions {
export const createExceptionListItem = async ({
_tags,
comment,
comments,
entries,
itemId,
listId,
@ -62,7 +66,7 @@ export const createExceptionListItem = async ({
const dateNow = new Date().toISOString();
const savedObject = await savedObjectsClient.create<ExceptionListSoSchema>(savedObjectType, {
_tags,
comment,
comments: transformComments({ comments, user }),
created_at: dateNow,
created_by: user,
description,

View file

@ -133,7 +133,7 @@ export class ExceptionListClient {
public createExceptionListItem = async ({
_tags,
comment,
comments,
description,
entries,
itemId,
@ -147,7 +147,7 @@ export class ExceptionListClient {
const { savedObjectsClient, user } = this;
return createExceptionListItem({
_tags,
comment,
comments,
description,
entries,
itemId,
@ -164,7 +164,7 @@ export class ExceptionListClient {
public updateExceptionListItem = async ({
_tags,
comment,
comments,
description,
entries,
id,
@ -178,7 +178,7 @@ export class ExceptionListClient {
const { savedObjectsClient, user } = this;
return updateExceptionListItem({
_tags,
comment,
comments,
description,
entries,
id,

View file

@ -7,7 +7,7 @@
import { SavedObjectsClientContract } from 'kibana/server';
import {
CommentOrUndefined,
CommentsPartialArray,
Description,
DescriptionOrUndefined,
EntriesArray,
@ -88,7 +88,7 @@ export interface GetExceptionListItemOptions {
export interface CreateExceptionListItemOptions {
_tags: _Tags;
comment: CommentOrUndefined;
comments: CommentsPartialArray;
entries: EntriesArray;
itemId: ItemId;
listId: ListId;
@ -102,7 +102,7 @@ export interface CreateExceptionListItemOptions {
export interface UpdateExceptionListItemOptions {
_tags: _TagsOrUndefined;
comment: CommentOrUndefined;
comments: CommentsPartialArray;
entries: EntriesArrayOrUndefined;
id: IdOrUndefined;
itemId: ItemIdOrUndefined;

View file

@ -18,7 +18,7 @@ import {
} from '../../../common/schemas';
import { SavedObjectType } from '../../saved_objects';
import { getSavedObjectType, transformSavedObjectsToFounExceptionList } from './utils';
import { getSavedObjectType, transformSavedObjectsToFoundExceptionList } from './utils';
interface FindExceptionListOptions {
namespaceType: NamespaceType;
@ -48,7 +48,7 @@ export const findExceptionList = async ({
sortOrder,
type: savedObjectType,
});
return transformSavedObjectsToFounExceptionList({ namespaceType, savedObjectsFindResponse });
return transformSavedObjectsToFoundExceptionList({ namespaceType, savedObjectsFindResponse });
};
export const getExceptionListFilter = ({

View file

@ -19,7 +19,7 @@ import {
} from '../../../common/schemas';
import { SavedObjectType } from '../../saved_objects';
import { getSavedObjectType, transformSavedObjectsToFounExceptionListItem } from './utils';
import { getSavedObjectType, transformSavedObjectsToFoundExceptionListItem } from './utils';
import { getExceptionList } from './get_exception_list';
interface FindExceptionListItemOptions {
@ -61,7 +61,7 @@ export const findExceptionListItem = async ({
sortOrder,
type: savedObjectType,
});
return transformSavedObjectsToFounExceptionListItem({
return transformSavedObjectsToFoundExceptionListItem({
namespaceType,
savedObjectsFindResponse,
});

View file

@ -7,7 +7,7 @@
import { SavedObjectsClientContract } from 'kibana/server';
import {
CommentOrUndefined,
CommentsPartialArray,
DescriptionOrUndefined,
EntriesArrayOrUndefined,
ExceptionListItemSchema,
@ -22,12 +22,16 @@ import {
_TagsOrUndefined,
} from '../../../common/schemas';
import { getSavedObjectType, transformSavedObjectUpdateToExceptionListItem } from './utils';
import {
getSavedObjectType,
transformComments,
transformSavedObjectUpdateToExceptionListItem,
} from './utils';
import { getExceptionListItem } from './get_exception_list_item';
interface UpdateExceptionListItemOptions {
id: IdOrUndefined;
comment: CommentOrUndefined;
comments: CommentsPartialArray;
_tags: _TagsOrUndefined;
name: NameOrUndefined;
description: DescriptionOrUndefined;
@ -44,7 +48,7 @@ interface UpdateExceptionListItemOptions {
export const updateExceptionListItem = async ({
_tags,
comment,
comments,
entries,
id,
savedObjectsClient,
@ -72,7 +76,7 @@ export const updateExceptionListItem = async ({
exceptionListItem.id,
{
_tags,
comment,
comments: transformComments({ comments, user }),
description,
entries,
meta,

View file

@ -7,6 +7,8 @@
import { SavedObject, SavedObjectsFindResponse, SavedObjectsUpdateResponse } from 'kibana/server';
import {
CommentsArrayOrUndefined,
CommentsPartialArrayOrUndefined,
ExceptionListItemSchema,
ExceptionListSchema,
ExceptionListSoSchema,
@ -125,7 +127,7 @@ export const transformSavedObjectToExceptionListItem = ({
const {
attributes: {
_tags,
comment,
comments,
created_at,
created_by,
description,
@ -147,7 +149,7 @@ export const transformSavedObjectToExceptionListItem = ({
// TODO: Do a throw if item_id or entries is not defined.
return {
_tags,
comment,
comments: comments ?? [],
created_at,
created_by,
description,
@ -179,7 +181,7 @@ export const transformSavedObjectUpdateToExceptionListItem = ({
const {
attributes: {
_tags,
comment,
comments,
description,
entries,
meta,
@ -196,7 +198,7 @@ export const transformSavedObjectUpdateToExceptionListItem = ({
// TODO: Do a throw if after the decode this is not the correct "list_type: list"
return {
_tags: _tags ?? exceptionListItem._tags,
comment: comment ?? exceptionListItem.comment,
comments: comments ?? exceptionListItem.comments,
created_at: exceptionListItem.created_at,
created_by: exceptionListItem.created_by,
description: description ?? exceptionListItem.description,
@ -215,7 +217,7 @@ export const transformSavedObjectUpdateToExceptionListItem = ({
};
};
export const transformSavedObjectsToFounExceptionListItem = ({
export const transformSavedObjectsToFoundExceptionListItem = ({
savedObjectsFindResponse,
namespaceType,
}: {
@ -232,7 +234,7 @@ export const transformSavedObjectsToFounExceptionListItem = ({
};
};
export const transformSavedObjectsToFounExceptionList = ({
export const transformSavedObjectsToFoundExceptionList = ({
savedObjectsFindResponse,
namespaceType,
}: {
@ -248,3 +250,24 @@ export const transformSavedObjectsToFounExceptionList = ({
total: savedObjectsFindResponse.total,
};
};
export const transformComments = ({
comments,
user,
}: {
comments: CommentsPartialArrayOrUndefined;
user: string;
}): CommentsArrayOrUndefined => {
const dateNow = new Date().toISOString();
if (comments != null) {
return comments.map((comment) => {
return {
comment: comment.comment,
created_at: comment.created_at ?? dateNow,
created_by: comment.created_by ?? user,
};
});
} else {
return comments;
}
};

View file

@ -439,7 +439,7 @@ describe('Exception helpers', () => {
describe('#getFormattedComments', () => {
test('it returns formatted comment object with username and timestamp', () => {
const payload = getExceptionItemMock().comment;
const payload = getExceptionItemMock().comments;
const result = getFormattedComments(payload);
expect(result[0].username).toEqual('user_name');
@ -447,7 +447,7 @@ describe('Exception helpers', () => {
});
test('it returns formatted timeline icon with comment users initial', () => {
const payload = getExceptionItemMock().comment;
const payload = getExceptionItemMock().comments;
const result = getFormattedComments(payload);
const wrapper = mount<React.ReactElement>(result[0].timelineIcon as React.ReactElement);
@ -456,7 +456,7 @@ describe('Exception helpers', () => {
});
test('it returns comment text', () => {
const payload = getExceptionItemMock().comment;
const payload = getExceptionItemMock().comments;
const result = getFormattedComments(payload);
const wrapper = mount<React.ReactElement>(result[0].children as React.ReactElement);

View file

@ -184,9 +184,9 @@ export const getDescriptionListContent = (
*/
export const getFormattedComments = (comments: Comment[]): EuiCommentProps[] =>
comments.map((comment) => ({
username: comment.user,
timestamp: moment(comment.timestamp).format('on MMM Do YYYY @ HH:mm:ss'),
username: comment.created_by,
timestamp: moment(comment.created_at).format('on MMM Do YYYY @ HH:mm:ss'),
event: i18n.COMMENT_EVENT,
timelineIcon: <EuiAvatar size="l" name={comment.user.toUpperCase()} />,
timelineIcon: <EuiAvatar size="l" name={comment.created_by.toUpperCase()} />,
children: <EuiText size="s">{comment.comment}</EuiText>,
}));

View file

@ -63,10 +63,10 @@ export const getExceptionItemMock = (): ExceptionListItemSchema => ({
namespace_type: 'single',
name: '',
description: 'This is a description',
comment: [
comments: [
{
user: 'user_name',
timestamp: '2020-04-23T00:19:13.289Z',
created_by: 'user_name',
created_at: '2020-04-23T00:19:13.289Z',
comment: 'Comment goes here',
},
],

View file

@ -57,8 +57,8 @@ export interface DescriptionListItem {
}
export interface Comment {
user: string;
timestamp: string;
created_by: string;
created_at: string;
comment: string;
}
@ -106,7 +106,7 @@ export interface ExceptionsPagination {
// TODO: Delete once types are updated
export interface ExceptionListItemSchema {
_tags: string[];
comment: Comment[];
comments: Comment[];
created_at: string;
created_by: string;
description?: string;

View file

@ -24,7 +24,7 @@ describe('ExceptionDetails', () => {
test('it renders no comments button if no comments exist', () => {
const exceptionItem = getExceptionItemMock();
exceptionItem.comment = [];
exceptionItem.comments = [];
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
@ -77,15 +77,15 @@ describe('ExceptionDetails', () => {
test('it renders comments plural if more than one', () => {
const exceptionItem = getExceptionItemMock();
exceptionItem.comment = [
exceptionItem.comments = [
{
user: 'user_1',
timestamp: '2020-04-23T00:19:13.289Z',
created_by: 'user_1',
created_at: '2020-04-23T00:19:13.289Z',
comment: 'Comment goes here',
},
{
user: 'user_2',
timestamp: '2020-04-23T00:19:13.289Z',
created_by: 'user_2',
created_at: '2020-04-23T00:19:13.289Z',
comment: 'Comment goes here',
},
];

View file

@ -49,9 +49,8 @@ const ExceptionDetailsComponent = ({
);
const commentsSection = useMemo((): JSX.Element => {
// TODO: return back to exceptionItem.comments once updated
const { comment } = exceptionItem;
if (comment.length > 0) {
const { comments } = exceptionItem;
if (comments.length > 0) {
return (
<EuiButtonEmpty
onClick={onCommentsClick}
@ -59,7 +58,9 @@ const ExceptionDetailsComponent = ({
size="xs"
data-test-subj="exceptionsViewerItemCommentsBtn"
>
{!showComments ? i18n.COMMENTS_SHOW(comment.length) : i18n.COMMENTS_HIDE(comment.length)}
{!showComments
? i18n.COMMENTS_SHOW(comments.length)
: i18n.COMMENTS_HIDE(comments.length)}
</EuiButtonEmpty>
);
} else {

View file

@ -21,7 +21,7 @@ storiesOf('Components|ExceptionItem', module)
.add('with os', () => {
const payload = getExceptionItemMock();
payload.description = '';
payload.comment = [];
payload.comments = [];
payload.entries = [
{
field: 'actingProcess.file.signer',
@ -44,7 +44,7 @@ storiesOf('Components|ExceptionItem', module)
.add('with description', () => {
const payload = getExceptionItemMock();
payload._tags = [];
payload.comment = [];
payload.comments = [];
payload.entries = [
{
field: 'actingProcess.file.signer',
@ -91,7 +91,7 @@ storiesOf('Components|ExceptionItem', module)
const payload = getExceptionItemMock();
payload._tags = [];
payload.description = '';
payload.comment = [];
payload.comments = [];
return (
<ExceptionItem

View file

@ -63,8 +63,7 @@ const ExceptionItemComponent = ({
}, [setShowComments, showComments]);
const formattedComments = useMemo((): EuiCommentProps[] => {
// TODO: return back to exceptionItem.comments once updated
return getFormattedComments(exceptionItem.comment);
return getFormattedComments(exceptionItem.comments);
}, [exceptionItem]);
const disableDelete = useMemo((): boolean => {