mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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:
parent
5f3c3c0354
commit
da5aa03583
38 changed files with 866 additions and 90 deletions
|
@ -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",
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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({});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
});
|
|
@ -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({});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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({});
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
40
x-pack/plugins/lists/common/schemas/types/comments.ts
Normal file
40
x-pack/plugins/lists/common/schemas/types/comments.ts
Normal 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>;
|
|
@ -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
|
||||
);
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = ({
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>,
|
||||
}));
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue