[Cases] Version cases and comment domain and apis (#161954)

## Summary

This PR versions the `cases` and `comment` domain objects and their
corresponding APIs. It was not possible to do them separately as I got
errors due to circular dependencies.

## Notable Changes
- The `Comment` type was renamed to `Attachment`
- The `Comments` type was renamed to `Attachments`
- The `*CommentRequestRt` type was renamed to `*AttachmentPayload`
- The `CommentType` type was  renamed to `AttachmentType`
- The `AttributesType*` type was renamed to `*AttachmentAttributes`
- The `*ResponseTypeUserRt` type was renamed to `*AttachmentRt`
- The word `comment` got replaced with the word `attachment` in all
types
- The `RelatedCaseInfo` type was renamed to `RelatedCase`
- The `CasesByAlertId` type was renamed to
`GetRelatedCasesByAlertResponse`

Depends on: https://github.com/elastic/kibana/pull/161783,
https://github.com/elastic/kibana/pull/162059

### Checklist

Delete any items that do not apply to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Christos Nasikas 2023-07-26 16:09:10 +03:00 committed by GitHub
parent da5554f77b
commit 2221ff8b55
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
388 changed files with 4913 additions and 4494 deletions

View file

@ -6,6 +6,14 @@
* Side Public License, v 1.
*/
/**
* This is being used by Cases in
* x-pack/plugins/cases/common/types/domain/case/v1.ts.
* Introducing a breaking change in this enum will
* force cases to create a version of the domain object
* which in turn will force cases to create a new version
* to most of the Cases APIs.
*/
export enum CaseStatuses {
open = 'open',
'in-progress' = 'in-progress',

View file

@ -1,41 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { CaseAssigneesRt } from './assignee';
describe('Assignee', () => {
describe('CaseAssigneesRt', () => {
const defaultRequest = [{ uid: '1' }, { uid: '2' }];
it('has expected attributes in request', () => {
const query = CaseAssigneesRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CaseAssigneesRt.decode([{ ...defaultRequest[0], foo: 'bar' }]);
expect(query).toStrictEqual({
_tag: 'Right',
right: [defaultRequest[0]],
});
});
it('removes foo:bar attributes from assignees', () => {
const query = CaseAssigneesRt.decode([{ uid: '1', foo: 'bar' }, { uid: '2' }]);
expect(query).toStrictEqual({
_tag: 'Right',
right: [{ uid: '1' }, { uid: '2' }],
});
});
});
});

View file

@ -1,765 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { PathReporter } from 'io-ts/lib/PathReporter';
import { ConnectorTypes } from '../../types/domain/connector/v1';
import {
RelatedCaseInfoRt,
SettingsRt,
CaseSeverity,
CaseFullExternalServiceRt,
CasesFindRequestRt,
CasesByAlertIDRequestRt,
CasePatchRequestRt,
CasesPatchRequestRt,
CasePushRequestParamsRt,
ExternalServiceResponseRt,
AllReportersFindRequestRt,
CasesBulkGetRequestRt,
CasesBulkGetResponseRt,
CasePostRequestRt,
CaseAttributesRt,
CasesRt,
CasesFindResponseRt,
CaseResolveResponseRt,
CasesFindRequestSearchFieldsRt,
CasesFindRequestSortFieldsRt,
} from './case';
import { CommentType } from './comment';
import { CaseStatuses } from './status';
const basicCase = {
owner: 'cases',
closed_at: null,
closed_by: null,
id: 'basic-case-id',
comments: [
{
comment: 'Solve this fast!',
type: CommentType.user,
id: 'basic-comment-id',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
owner: 'cases',
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
version: 'WzQ3LDFc',
},
],
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
connector: {
id: 'none',
name: 'My Connector',
type: ConnectorTypes.none,
fields: null,
},
description: 'Security banana Issue',
severity: CaseSeverity.LOW,
duration: null,
external_service: null,
status: CaseStatuses.open,
tags: ['coke', 'pepsi'],
title: 'Another horrible breach!!',
totalComment: 1,
totalAlerts: 0,
updated_at: '2020-02-20T15:02:57.995Z',
updated_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
version: 'WzQ3LDFd',
settings: {
syncAlerts: true,
},
// damaged_raccoon uid
assignees: [{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }],
category: null,
};
describe('Case', () => {
describe('RelatedCaseInfoRt', () => {
const defaultRequest = {
id: 'basic-case-id',
title: 'basic-case-title',
description: 'this is a simple description',
status: CaseStatuses.open,
createdAt: '2023-01-17T09:46:29.813Z',
totals: {
alerts: 5,
userComments: 2,
},
};
it('has expected attributes in request', () => {
const query = RelatedCaseInfoRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = RelatedCaseInfoRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from totals', () => {
const query = RelatedCaseInfoRt.decode({
...defaultRequest,
totals: { ...defaultRequest.totals, foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('SettingsRt', () => {
it('has expected attributes in request', () => {
const query = SettingsRt.decode({ syncAlerts: true });
expect(query).toStrictEqual({
_tag: 'Right',
right: { syncAlerts: true },
});
});
it('removes foo:bar attributes from request', () => {
const query = SettingsRt.decode({ syncAlerts: false, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: { syncAlerts: false },
});
});
});
describe('CaseFullExternalServiceRt', () => {
const defaultRequest = {
connector_id: 'servicenow-1',
connector_name: 'My SN connector',
external_id: 'external_id',
external_title: 'external title',
external_url: 'basicPush.com',
pushed_at: '2023-01-17T09:46:29.813Z',
pushed_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
};
it('has expected attributes in request', () => {
const query = CaseFullExternalServiceRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CaseFullExternalServiceRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from pushed_by', () => {
const query = CaseFullExternalServiceRt.decode({
...defaultRequest,
pushed_by: { ...defaultRequest.pushed_by, foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CaseAttributesRt', () => {
const defaultRequest = {
description: 'A description',
status: CaseStatuses.open,
tags: ['new', 'case'],
title: 'My new case',
connector: {
id: '123',
name: 'My connector',
type: ConnectorTypes.jira,
fields: { issueType: 'Task', priority: 'High', parent: null },
},
settings: {
syncAlerts: true,
},
owner: 'cases',
severity: CaseSeverity.LOW,
assignees: [{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }],
duration: null,
closed_at: null,
closed_by: null,
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
external_service: null,
updated_at: '2020-02-20T15:02:57.995Z',
updated_by: null,
category: null,
};
it('has expected attributes in request', () => {
const query = CaseAttributesRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CaseAttributesRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from connector', () => {
const query = CaseAttributesRt.decode({
...defaultRequest,
connector: { ...defaultRequest.connector, foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from created_by', () => {
const query = CaseAttributesRt.decode({
...defaultRequest,
created_by: { ...defaultRequest.created_by, foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasePostRequestRt', () => {
const defaultRequest = {
description: 'A description',
tags: ['new', 'case'],
title: 'My new case',
connector: {
id: '123',
name: 'My connector',
type: ConnectorTypes.jira,
fields: { issueType: 'Task', priority: 'High', parent: null },
},
settings: {
syncAlerts: true,
},
owner: 'cases',
severity: CaseSeverity.LOW,
assignees: [{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }],
};
it('has expected attributes in request', () => {
const query = CasePostRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasePostRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from connector', () => {
const query = CasePostRequestRt.decode({
...defaultRequest,
connector: { ...defaultRequest.connector, foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasesFindRequestRt', () => {
const defaultRequest = {
tags: ['new', 'case'],
status: CaseStatuses.open,
severity: CaseSeverity.LOW,
assignees: ['damaged_racoon'],
reporters: ['damaged_racoon'],
defaultSearchOperator: 'AND',
from: 'now',
page: '1',
perPage: '10',
search: 'search text',
searchFields: ['title', 'description'],
to: '1w',
sortOrder: 'desc',
sortField: 'createdAt',
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = CasesFindRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: { ...defaultRequest, page: 1, perPage: 10 },
});
});
it('removes foo:bar attributes from request', () => {
const query = CasesFindRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: { ...defaultRequest, page: 1, perPage: 10 },
});
});
const searchFields = Object.keys(CasesFindRequestSearchFieldsRt.keys);
it.each(searchFields)('succeeds with %s as searchFields', (field) => {
const query = CasesFindRequestRt.decode({ ...defaultRequest, searchFields: field });
expect(query).toStrictEqual({
_tag: 'Right',
right: { ...defaultRequest, searchFields: field, page: 1, perPage: 10 },
});
});
const sortFields = Object.keys(CasesFindRequestSortFieldsRt.keys);
it.each(sortFields)('succeeds with %s as sortField', (sortField) => {
const query = CasesFindRequestRt.decode({ ...defaultRequest, sortField });
expect(query).toStrictEqual({
_tag: 'Right',
right: { ...defaultRequest, sortField, page: 1, perPage: 10 },
});
});
it('removes rootSearchField when passed', () => {
expect(
PathReporter.report(
CasesFindRequestRt.decode({ ...defaultRequest, rootSearchField: ['foobar'] })
)
).toContain('No errors!');
});
describe('errors', () => {
it('throws error when invalid searchField passed', () => {
expect(
PathReporter.report(
CasesFindRequestRt.decode({ ...defaultRequest, searchFields: 'foobar' })
)
).not.toContain('No errors!');
});
it('throws error when invalid sortField passed', () => {
expect(
PathReporter.report(CasesFindRequestRt.decode({ ...defaultRequest, sortField: 'foobar' }))
).not.toContain('No errors!');
});
it('succeeds when valid parameters passed', () => {
expect(PathReporter.report(CasesFindRequestRt.decode(defaultRequest))).toContain(
'No errors!'
);
});
});
});
describe('CasesByAlertIDRequestRt', () => {
it('has expected attributes in request', () => {
const query = CasesByAlertIDRequestRt.decode({ owner: 'cases' });
expect(query).toStrictEqual({
_tag: 'Right',
right: { owner: 'cases' },
});
});
it('removes foo:bar attributes from request', () => {
const query = CasesByAlertIDRequestRt.decode({ owner: ['cases'], foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: { owner: ['cases'] },
});
});
});
describe('CaseResolveResponseRt', () => {
const defaultRequest = {
case: { ...basicCase },
outcome: 'exactMatch',
alias_target_id: 'sample-target-id',
alias_purpose: 'savedObjectConversion',
};
it('has expected attributes in request', () => {
const query = CaseResolveResponseRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CaseResolveResponseRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasesFindResponseRt', () => {
const defaultRequest = {
cases: [{ ...basicCase }],
page: 1,
per_page: 10,
total: 20,
count_open_cases: 10,
count_in_progress_cases: 5,
count_closed_cases: 5,
};
it('has expected attributes in request', () => {
const query = CasesFindResponseRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasesFindResponseRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from cases', () => {
const query = CasesFindResponseRt.decode({
...defaultRequest,
cases: [{ ...basicCase, foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasePatchRequestRt', () => {
const defaultRequest = {
id: 'basic-case-id',
version: 'WzQ3LDFd',
description: 'Updated description',
};
it('has expected attributes in request', () => {
const query = CasePatchRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasePatchRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasesPatchRequestRt', () => {
const defaultRequest = {
cases: [
{
id: 'basic-case-id',
version: 'WzQ3LDFd',
description: 'Updated description',
},
],
};
it('has expected attributes in request', () => {
const query = CasesPatchRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasesPatchRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasesRt', () => {
const defaultRequest = [
{
...basicCase,
},
];
it('has expected attributes in request', () => {
const query = CasesRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasesRt.decode([{ ...defaultRequest[0], foo: 'bar' }]);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasePushRequestParamsRt', () => {
const defaultRequest = {
case_id: 'basic-case-id',
connector_id: 'basic-connector-id',
};
it('has expected attributes in request', () => {
const query = CasePushRequestParamsRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasePushRequestParamsRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('ExternalServiceResponseRt', () => {
const defaultRequest = {
title: 'case_title',
id: 'basic-case-id',
pushedDate: '2020-02-19T23:06:33.798Z',
url: 'https://atlassian.com',
comments: [
{
commentId: 'basic-comment-id',
pushedDate: '2020-02-19T23:06:33.798Z',
externalCommentId: 'external-comment-id',
},
],
};
it('has expected attributes in request', () => {
const query = ExternalServiceResponseRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = ExternalServiceResponseRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from comments', () => {
const query = ExternalServiceResponseRt.decode({
...defaultRequest,
comments: [{ ...defaultRequest.comments[0], foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('AllReportersFindRequestRt', () => {
const defaultRequest = {
owner: ['cases', 'security-solution'],
};
it('has expected attributes in request', () => {
const query = AllReportersFindRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = AllReportersFindRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasesBulkGetRequestRt', () => {
const defaultRequest = {
ids: ['case-1', 'case-2'],
};
it('has expected attributes in request', () => {
const query = CasesBulkGetRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasesBulkGetRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasesBulkGetResponseRt', () => {
const defaultRequest = {
cases: [basicCase],
errors: [
{
error: 'error',
message: 'error-message',
status: 403,
caseId: 'basic-case-id',
},
],
};
it('has expected attributes in request', () => {
const query = CasesBulkGetResponseRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasesBulkGetResponseRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from cases', () => {
const query = CasesBulkGetResponseRt.decode({
...defaultRequest,
cases: [{ ...defaultRequest.cases[0], foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: { ...defaultRequest, cases: defaultRequest.cases },
});
});
it('removes foo:bar attributes from errors', () => {
const query = CasesBulkGetResponseRt.decode({
...defaultRequest,
errors: [{ ...defaultRequest.errors[0], foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
});

View file

@ -1,98 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
SingleFileAttachmentMetadataRt,
FileAttachmentMetadataRt,
BulkDeleteFileAttachmentsRequestRt,
} from './files';
describe('Files', () => {
describe('SingleFileAttachmentMetadataRt', () => {
const defaultRequest = {
created: '2020-02-19T23:06:33.798Z',
extension: 'png',
mimeType: 'image/png',
name: 'my-super-cool-screenshot',
};
it('has expected attributes in request', () => {
const query = SingleFileAttachmentMetadataRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = SingleFileAttachmentMetadataRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('FileAttachmentMetadataRt', () => {
const defaultRequest = {
created: '2020-02-19T23:06:33.798Z',
extension: 'png',
mimeType: 'image/png',
name: 'my-super-cool-screenshot',
};
it('has expected attributes in request', () => {
const query = FileAttachmentMetadataRt.decode({ files: [defaultRequest] });
expect(query).toStrictEqual({
_tag: 'Right',
right: {
files: [
{
...defaultRequest,
},
],
},
});
});
it('removes foo:bar attributes from request', () => {
const query = FileAttachmentMetadataRt.decode({ files: [{ ...defaultRequest, foo: 'bar' }] });
expect(query).toStrictEqual({
_tag: 'Right',
right: {
files: [
{
...defaultRequest,
},
],
},
});
});
});
describe('BulkDeleteFileAttachmentsRequestRt', () => {
it('has expected attributes in request', () => {
const query = BulkDeleteFileAttachmentsRequestRt.decode({ ids: ['abc', 'xyz'] });
expect(query).toStrictEqual({
_tag: 'Right',
right: { ids: ['abc', 'xyz'] },
});
});
it('removes foo:bar attributes from request', () => {
const query = BulkDeleteFileAttachmentsRequestRt.decode({ ids: ['abc', 'xyz'], foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: { ids: ['abc', 'xyz'] },
});
});
});
});

View file

@ -1,36 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import * as rt from 'io-ts';
import { MAX_DELETE_FILES } from '../../../constants';
import { limitedArraySchema, NonEmptyString } from '../../../schema';
export const SingleFileAttachmentMetadataRt = rt.strict({
name: rt.string,
extension: rt.string,
mimeType: rt.string,
created: rt.string,
});
export const FileAttachmentMetadataRt = rt.strict({
files: rt.array(SingleFileAttachmentMetadataRt),
});
export type FileAttachmentMetadata = rt.TypeOf<typeof FileAttachmentMetadataRt>;
const MIN_DELETE_IDS = 1;
export const BulkDeleteFileAttachmentsRequestRt = rt.strict({
ids: limitedArraySchema({
codec: NonEmptyString,
min: MIN_DELETE_IDS,
max: MAX_DELETE_FILES,
fieldName: 'ids',
}),
});
export type BulkDeleteFileAttachmentsRequest = rt.TypeOf<typeof BulkDeleteFileAttachmentsRequestRt>;

View file

@ -1,962 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { PathReporter } from 'io-ts/lib/PathReporter';
import {
CommentAttributesBasicRt,
CommentType,
ContextTypeUserRt,
AlertCommentRequestRt,
ActionsCommentRequestRt,
ExternalReferenceStorageType,
ExternalReferenceRt,
PersistableStateAttachmentRt,
CommentRequestRt,
CommentRt,
CommentResponseTypeUserRt,
CommentResponseTypeAlertsRt,
CommentResponseTypeActionsRt,
CommentResponseTypeExternalReferenceRt,
CommentResponseTypePersistableStateRt,
CommentPatchRequestRt,
CommentPatchAttributesRt,
CommentsFindResponseRt,
FindCommentsQueryParamsRt,
BulkCreateCommentRequestRt,
BulkGetAttachmentsRequestRt,
BulkGetAttachmentsResponseRt,
} from '.';
import { MAX_COMMENT_LENGTH, MAX_BULK_CREATE_ATTACHMENTS } from '../../../constants';
describe('Comments', () => {
describe('CommentAttributesBasicRt', () => {
const defaultRequest = {
created_at: '2019-11-25T22:32:30.608Z',
created_by: {
full_name: 'elastic',
email: 'testemail@elastic.co',
username: 'elastic',
},
owner: 'cases',
updated_at: null,
updated_by: null,
pushed_at: null,
pushed_by: null,
};
it('has expected attributes in request', () => {
const query = CommentAttributesBasicRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CommentAttributesBasicRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('ContextTypeUserRt', () => {
const defaultRequest = {
comment: 'This is a sample comment',
type: CommentType.user,
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = ContextTypeUserRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = ContextTypeUserRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('AlertCommentRequestRt', () => {
const defaultRequest = {
alertId: 'alert-id-1',
index: 'alert-index-1',
type: CommentType.alert,
owner: 'cases',
rule: {
id: 'rule-id-1',
name: 'Awesome rule',
},
};
it('has expected attributes in request', () => {
const query = AlertCommentRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = AlertCommentRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from rule', () => {
const query = AlertCommentRequestRt.decode({
...defaultRequest,
rule: { id: 'rule-id-1', name: 'Awesome rule', foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('ActionsCommentRequestRt', () => {
const defaultRequest = {
type: CommentType.actions,
comment: 'I just isolated the host!',
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
},
],
type: 'isolate',
},
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = ActionsCommentRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = ActionsCommentRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from actions', () => {
const query = ActionsCommentRequestRt.decode({
...defaultRequest,
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
},
],
type: 'isolate',
foo: 'bar',
},
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from targets', () => {
const query = ActionsCommentRequestRt.decode({
...defaultRequest,
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
foo: 'bar',
},
],
type: 'isolate',
},
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('ExternalReferenceRt', () => {
const defaultRequest = {
type: CommentType.externalReference,
externalReferenceId: 'my-id',
externalReferenceStorage: { type: ExternalReferenceStorageType.elasticSearchDoc },
externalReferenceAttachmentTypeId: '.test',
externalReferenceMetadata: { test_foo: 'foo' },
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = ExternalReferenceRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = ExternalReferenceRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from externalReferenceStorage', () => {
const query = ExternalReferenceRt.decode({
...defaultRequest,
externalReferenceStorage: {
type: ExternalReferenceStorageType.elasticSearchDoc,
foo: 'bar',
},
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from externalReferenceStorage with soType', () => {
const query = ExternalReferenceRt.decode({
...defaultRequest,
externalReferenceStorage: {
type: ExternalReferenceStorageType.savedObject,
soType: 'awesome',
foo: 'bar',
},
});
expect(query).toStrictEqual({
_tag: 'Right',
right: {
...defaultRequest,
externalReferenceStorage: {
type: ExternalReferenceStorageType.savedObject,
soType: 'awesome',
},
},
});
});
});
describe('PersistableStateAttachmentRt', () => {
const defaultRequest = {
type: CommentType.persistableState,
persistableStateAttachmentState: { test_foo: 'foo' },
persistableStateAttachmentTypeId: '.test',
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = PersistableStateAttachmentRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = PersistableStateAttachmentRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from persistableStateAttachmentState', () => {
const query = PersistableStateAttachmentRt.decode({
...defaultRequest,
persistableStateAttachmentState: { test_foo: 'foo', foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: {
...defaultRequest,
persistableStateAttachmentState: { test_foo: 'foo', foo: 'bar' },
},
});
});
});
describe('CommentRequestRt', () => {
const defaultRequest = {
comment: 'Solve this fast!',
type: CommentType.user,
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = CommentRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CommentRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
describe('errors', () => {
describe('commentType: user', () => {
it('throws error when comment is too long', () => {
const longComment = 'x'.repeat(MAX_COMMENT_LENGTH + 1);
expect(
PathReporter.report(
CommentRequestRt.decode({ ...defaultRequest, comment: longComment })
)
).toContain('The length of the comment is too long. The maximum length is 30000.');
});
it('throws error when comment is empty', () => {
expect(
PathReporter.report(CommentRequestRt.decode({ ...defaultRequest, comment: '' }))
).toContain('The comment field cannot be an empty string.');
});
it('throws error when comment string of empty characters', () => {
expect(
PathReporter.report(CommentRequestRt.decode({ ...defaultRequest, comment: ' ' }))
).toContain('The comment field cannot be an empty string.');
});
});
describe('commentType: action', () => {
const request = {
type: CommentType.actions,
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
},
],
type: 'isolate',
},
owner: 'cases',
};
it('throws error when comment is too long', () => {
const longComment = 'x'.repeat(MAX_COMMENT_LENGTH + 1);
expect(
PathReporter.report(CommentRequestRt.decode({ ...request, comment: longComment }))
).toContain('The length of the comment is too long. The maximum length is 30000.');
});
it('throws error when comment is empty', () => {
expect(
PathReporter.report(CommentRequestRt.decode({ ...request, comment: '' }))
).toContain('The comment field cannot be an empty string.');
});
it('throws error when comment string of empty characters', () => {
expect(
PathReporter.report(CommentRequestRt.decode({ ...request, comment: ' ' }))
).toContain('The comment field cannot be an empty string.');
});
});
});
});
describe('CommentRt', () => {
const defaultRequest = {
comment: 'Solve this fast!',
type: CommentType.user,
owner: 'cases',
id: 'basic-comment-id',
version: 'WzQ3LDFc',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
};
it('has expected attributes in request', () => {
const query = CommentRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CommentRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CommentResponseTypeUserRt', () => {
const defaultRequest = {
comment: 'Solve this fast!',
type: CommentType.user,
owner: 'cases',
id: 'basic-comment-id',
version: 'WzQ3LDFc',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
};
it('has expected attributes in request', () => {
const query = CommentResponseTypeUserRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CommentResponseTypeUserRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CommentResponseTypeAlertsRt', () => {
const defaultRequest = {
alertId: 'alert-id-1',
index: 'alert-index-1',
type: CommentType.alert,
id: 'alert-comment-id',
owner: 'cases',
rule: {
id: 'rule-id-1',
name: 'Awesome rule',
},
version: 'WzQ3LDFc',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
};
it('has expected attributes in request', () => {
const query = CommentResponseTypeAlertsRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CommentResponseTypeAlertsRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from created_by', () => {
const query = CommentResponseTypeAlertsRt.decode({
...defaultRequest,
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
foo: 'bar',
},
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CommentResponseTypeActionsRt', () => {
const defaultRequest = {
type: CommentType.actions,
comment: 'I just isolated the host!',
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
},
],
type: 'isolate',
},
owner: 'cases',
id: 'basic-comment-id',
version: 'WzQ3LDFc',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
};
it('has expected attributes in request', () => {
const query = CommentResponseTypeActionsRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CommentResponseTypeActionsRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CommentResponseTypeExternalReferenceRt', () => {
const defaultRequest = {
type: CommentType.externalReference,
externalReferenceId: 'my-id',
externalReferenceStorage: { type: ExternalReferenceStorageType.elasticSearchDoc },
externalReferenceAttachmentTypeId: '.test',
externalReferenceMetadata: { test_foo: 'foo' },
owner: 'cases',
id: 'basic-comment-id',
version: 'WzQ3LDFc',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
};
it('has expected attributes in request', () => {
const query = CommentResponseTypeExternalReferenceRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CommentResponseTypeExternalReferenceRt.decode({
...defaultRequest,
foo: 'bar',
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CommentResponseTypePersistableStateRt', () => {
const defaultRequest = {
type: CommentType.persistableState,
persistableStateAttachmentState: { test_foo: 'foo' },
persistableStateAttachmentTypeId: '.test',
owner: 'cases',
id: 'basic-comment-id',
version: 'WzQ3LDFc',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
};
it('has expected attributes in request', () => {
const query = CommentResponseTypePersistableStateRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CommentResponseTypePersistableStateRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CommentPatchRequestRt', () => {
const defaultRequest = {
alertId: 'alert-id-1',
index: 'alert-index-1',
type: CommentType.alert,
id: 'alert-comment-id',
owner: 'cases',
rule: {
id: 'rule-id-1',
name: 'Awesome rule',
},
version: 'WzQ3LDFc',
};
it('has expected attributes in request', () => {
const query = CommentPatchRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CommentPatchRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CommentPatchAttributesRt', () => {
const defaultRequest = {
type: CommentType.actions,
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
},
],
type: 'isolate',
},
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = CommentPatchAttributesRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CommentPatchAttributesRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CommentsFindResponseRt', () => {
const defaultRequest = {
comments: [
{
comment: 'Solve this fast!',
type: CommentType.user,
owner: 'cases',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
id: 'basic-comment-id',
version: 'WzQ3LDFc',
},
],
page: 1,
per_page: 10,
total: 1,
};
it('has expected attributes in request', () => {
const query = CommentsFindResponseRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CommentsFindResponseRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from comments', () => {
const query = CommentsFindResponseRt.decode({
...defaultRequest,
comments: [{ ...defaultRequest.comments[0], foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('FindCommentsQueryParamsRt', () => {
const defaultRequest = {
page: 1,
perPage: 10,
sortOrder: 'asc',
};
it('has expected attributes in request', () => {
const query = FindCommentsQueryParamsRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = FindCommentsQueryParamsRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('BulkCreateCommentRequestRt', () => {
const defaultRequest = [
{
comment: 'Solve this fast!',
type: CommentType.user,
owner: 'cases',
},
];
it('has expected attributes in request', () => {
const query = BulkCreateCommentRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = BulkCreateCommentRequestRt.decode([
{ comment: 'Solve this fast!', type: CommentType.user, owner: 'cases', foo: 'bar' },
]);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
describe('errors', () => {
it(`throws error when attachments are more than ${MAX_BULK_CREATE_ATTACHMENTS}`, () => {
const comment = {
comment: 'Solve this fast!',
type: CommentType.user,
owner: 'cases',
};
const attachments = Array(MAX_BULK_CREATE_ATTACHMENTS + 1).fill(comment);
expect(PathReporter.report(BulkCreateCommentRequestRt.decode(attachments))).toContain(
`The length of the field attachments is too long. Array must be of length <= ${MAX_BULK_CREATE_ATTACHMENTS}.`
);
});
it(`no errors when empty array of attachments`, () => {
expect(PathReporter.report(BulkCreateCommentRequestRt.decode([]))).toStrictEqual([
'No errors!',
]);
});
});
});
describe('BulkGetAttachmentsRequestRt', () => {
it('has expected attributes in request', () => {
const query = BulkGetAttachmentsRequestRt.decode({ ids: ['abc', 'xyz'] });
expect(query).toStrictEqual({
_tag: 'Right',
right: { ids: ['abc', 'xyz'] },
});
});
it('removes foo:bar attributes from request', () => {
const query = BulkGetAttachmentsRequestRt.decode({ ids: ['abc', 'xyz'], foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: { ids: ['abc', 'xyz'] },
});
});
});
describe('BulkGetAttachmentsResponseRt', () => {
const defaultRequest = {
attachments: [
{
comment: 'Solve this fast!',
type: CommentType.user,
owner: 'cases',
id: 'basic-comment-id',
version: 'WzQ3LDFc',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
},
],
errors: [
{
error: 'error',
message: 'not found',
status: 404,
attachmentId: 'abc',
},
],
};
it('has expected attributes in request', () => {
const query = BulkGetAttachmentsResponseRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = BulkGetAttachmentsResponseRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from attachments', () => {
const query = BulkGetAttachmentsResponseRt.decode({
...defaultRequest,
attachments: [{ ...defaultRequest.attachments[0], foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from errors', () => {
const query = BulkGetAttachmentsResponseRt.decode({
...defaultRequest,
errors: [{ ...defaultRequest.errors[0], foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
});

View file

@ -1,400 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import * as rt from 'io-ts';
import {
MAX_BULK_GET_ATTACHMENTS,
MAX_COMMENTS_PER_PAGE,
MAX_COMMENT_LENGTH,
MAX_BULK_CREATE_ATTACHMENTS,
} from '../../../constants';
import { limitedArraySchema, paginationSchema, limitedStringSchema } from '../../../schema';
import { jsonValueRt } from '../../runtime_types';
import { UserRt } from '../../user';
export * from './files';
export const CommentAttributesBasicRt = rt.strict({
created_at: rt.string,
created_by: UserRt,
owner: rt.string,
pushed_at: rt.union([rt.string, rt.null]),
pushed_by: rt.union([UserRt, rt.null]),
updated_at: rt.union([rt.string, rt.null]),
updated_by: rt.union([UserRt, rt.null]),
});
export enum CommentType {
user = 'user',
alert = 'alert',
actions = 'actions',
externalReference = 'externalReference',
persistableState = 'persistableState',
}
export enum IsolateHostActionType {
isolate = 'isolate',
unisolate = 'unisolate',
}
export const ContextTypeUserRt = rt.strict({
comment: rt.string,
type: rt.literal(CommentType.user),
owner: rt.string,
});
/**
* This defines the structure of how alerts (generated or user attached) are stored in saved objects documents. It also
* represents of an alert after it has been transformed. A generated alert will be transformed by the connector so that
* it matches this structure. User attached alerts do not need to be transformed.
*/
export const AlertCommentRequestRt = rt.strict({
type: rt.literal(CommentType.alert),
alertId: rt.union([rt.array(rt.string), rt.string]),
index: rt.union([rt.array(rt.string), rt.string]),
rule: rt.strict({
id: rt.union([rt.string, rt.null]),
name: rt.union([rt.string, rt.null]),
}),
owner: rt.string,
});
export const ActionsCommentRequestRt = rt.strict({
type: rt.literal(CommentType.actions),
comment: rt.string,
actions: rt.strict({
targets: rt.array(
rt.strict({
hostname: rt.string,
endpointId: rt.string,
})
),
type: rt.string,
}),
owner: rt.string,
});
export enum ExternalReferenceStorageType {
savedObject = 'savedObject',
elasticSearchDoc = 'elasticSearchDoc',
}
const ExternalReferenceStorageNoSORt = rt.strict({
type: rt.literal(ExternalReferenceStorageType.elasticSearchDoc),
});
const ExternalReferenceStorageSORt = rt.strict({
type: rt.literal(ExternalReferenceStorageType.savedObject),
soType: rt.string,
});
export const ExternalReferenceBaseRt = rt.strict({
externalReferenceAttachmentTypeId: rt.string,
externalReferenceMetadata: rt.union([rt.null, rt.record(rt.string, jsonValueRt)]),
type: rt.literal(CommentType.externalReference),
owner: rt.string,
});
export const ExternalReferenceNoSORt = rt.strict({
...ExternalReferenceBaseRt.type.props,
externalReferenceId: rt.string,
externalReferenceStorage: ExternalReferenceStorageNoSORt,
});
export const ExternalReferenceSORt = rt.strict({
...ExternalReferenceBaseRt.type.props,
externalReferenceId: rt.string,
externalReferenceStorage: ExternalReferenceStorageSORt,
});
// externalReferenceId is missing.
export const ExternalReferenceSOWithoutRefsRt = rt.strict({
...ExternalReferenceBaseRt.type.props,
externalReferenceStorage: ExternalReferenceStorageSORt,
});
export const ExternalReferenceRt = rt.union([ExternalReferenceNoSORt, ExternalReferenceSORt]);
export const ExternalReferenceWithoutRefsRt = rt.union([
ExternalReferenceNoSORt,
ExternalReferenceSOWithoutRefsRt,
]);
export const PersistableStateAttachmentRt = rt.strict({
type: rt.literal(CommentType.persistableState),
owner: rt.string,
persistableStateAttachmentTypeId: rt.string,
persistableStateAttachmentState: rt.record(rt.string, jsonValueRt),
});
const AttributesTypeUserRt = rt.intersection([ContextTypeUserRt, CommentAttributesBasicRt]);
export const AttributesTypeAlertsRt = rt.intersection([
AlertCommentRequestRt,
CommentAttributesBasicRt,
]);
const AttributesTypeActionsRt = rt.intersection([
ActionsCommentRequestRt,
CommentAttributesBasicRt,
]);
const AttributesTypeExternalReferenceRt = rt.intersection([
ExternalReferenceRt,
CommentAttributesBasicRt,
]);
const AttributesTypeExternalReferenceWithoutRefsRt = rt.intersection([
ExternalReferenceWithoutRefsRt,
CommentAttributesBasicRt,
]);
const AttributesTypeExternalReferenceNoSORt = rt.intersection([
ExternalReferenceNoSORt,
CommentAttributesBasicRt,
]);
const AttributesTypeExternalReferenceSORt = rt.intersection([
ExternalReferenceSORt,
CommentAttributesBasicRt,
]);
const AttributesTypePersistableStateRt = rt.intersection([
PersistableStateAttachmentRt,
CommentAttributesBasicRt,
]);
export const CommentAttributesRt = rt.union([
AttributesTypeUserRt,
AttributesTypeAlertsRt,
AttributesTypeActionsRt,
AttributesTypeExternalReferenceRt,
AttributesTypePersistableStateRt,
]);
const CommentAttributesNoSORt = rt.union([
AttributesTypeUserRt,
AttributesTypeAlertsRt,
AttributesTypeActionsRt,
AttributesTypeExternalReferenceNoSORt,
AttributesTypePersistableStateRt,
]);
const CommentAttributesWithoutRefsRt = rt.union([
AttributesTypeUserRt,
AttributesTypeAlertsRt,
AttributesTypeActionsRt,
AttributesTypeExternalReferenceWithoutRefsRt,
AttributesTypePersistableStateRt,
]);
const BasicCommentRequestRt = rt.union([
ContextTypeUserRt,
AlertCommentRequestRt,
ActionsCommentRequestRt,
ExternalReferenceNoSORt,
PersistableStateAttachmentRt,
]);
export const CommentRequestRt = rt.union([
rt.strict({
comment: limitedStringSchema({ fieldName: 'comment', min: 1, max: MAX_COMMENT_LENGTH }),
type: rt.literal(CommentType.user),
owner: rt.string,
}),
AlertCommentRequestRt,
rt.strict({
type: rt.literal(CommentType.actions),
comment: limitedStringSchema({ fieldName: 'comment', min: 1, max: MAX_COMMENT_LENGTH }),
actions: rt.strict({
targets: rt.array(
rt.strict({
hostname: rt.string,
endpointId: rt.string,
})
),
type: rt.string,
}),
owner: rt.string,
}),
ExternalReferenceNoSORt,
ExternalReferenceSORt,
PersistableStateAttachmentRt,
]);
export const CommentRequestWithoutRefsRt = rt.union([
BasicCommentRequestRt,
ExternalReferenceSOWithoutRefsRt,
]);
export const CommentRt = rt.intersection([
CommentAttributesRt,
rt.strict({
id: rt.string,
version: rt.string,
}),
]);
export const CommentResponseTypeUserRt = rt.intersection([
AttributesTypeUserRt,
rt.strict({
id: rt.string,
version: rt.string,
}),
]);
export const CommentResponseTypeAlertsRt = rt.intersection([
AttributesTypeAlertsRt,
rt.strict({
id: rt.string,
version: rt.string,
}),
]);
export const CommentResponseTypeActionsRt = rt.intersection([
AttributesTypeActionsRt,
rt.strict({
id: rt.string,
version: rt.string,
}),
]);
export const CommentResponseTypeExternalReferenceRt = rt.intersection([
AttributesTypeExternalReferenceRt,
rt.strict({
id: rt.string,
version: rt.string,
}),
]);
export const CommentResponseTypePersistableStateRt = rt.intersection([
AttributesTypePersistableStateRt,
rt.strict({
id: rt.string,
version: rt.string,
}),
]);
export const CommentPatchRequestRt = rt.intersection([
/**
* Partial updates are not allowed.
* We want to prevent the user for changing the type without removing invalid fields.
*
* injectAttachmentSOAttributesFromRefsForPatch is dependent on this assumption.
* The consumers of the persistable attachment service should always get the
* persistableStateAttachmentState on a patch.
*/
CommentRequestRt,
rt.strict({ id: rt.string, version: rt.string }),
]);
/**
* This type is used by the CaseService.
* Because the type for the attributes of savedObjectClient update function is Partial<T>
* we need to make all of our attributes partial too.
* We ensure that partial updates of CommentContext is not going to happen inside the patch comment route.
*/
export const CommentPatchAttributesRt = rt.intersection([
rt.union([
rt.exact(rt.partial(ContextTypeUserRt.type.props)),
rt.exact(rt.partial(AlertCommentRequestRt.type.props)),
rt.exact(rt.partial(ActionsCommentRequestRt.type.props)),
rt.exact(rt.partial(ExternalReferenceNoSORt.type.props)),
rt.exact(rt.partial(ExternalReferenceSORt.type.props)),
rt.exact(rt.partial(PersistableStateAttachmentRt.type.props)),
]),
rt.exact(rt.partial(CommentAttributesBasicRt.type.props)),
]);
export const CommentsFindResponseRt = rt.strict({
comments: rt.array(CommentRt),
page: rt.number,
per_page: rt.number,
total: rt.number,
});
export const CommentsRt = rt.array(CommentRt);
export const FindCommentsQueryParamsRt = rt.intersection([
rt.exact(
rt.partial({
/**
* Order to sort the response
*/
sortOrder: rt.union([rt.literal('desc'), rt.literal('asc')]),
})
),
paginationSchema({ maxPerPage: MAX_COMMENTS_PER_PAGE }),
]);
export const BulkCreateCommentRequestRt = limitedArraySchema({
codec: CommentRequestRt,
min: 0,
max: MAX_BULK_CREATE_ATTACHMENTS,
fieldName: 'attachments',
});
export const BulkGetAttachmentsRequestRt = rt.strict({
ids: limitedArraySchema({
codec: rt.string,
min: 1,
max: MAX_BULK_GET_ATTACHMENTS,
fieldName: 'ids',
}),
});
export const BulkGetAttachmentsResponseRt = rt.strict({
attachments: CommentsRt,
errors: rt.array(
rt.strict({
error: rt.string,
message: rt.string,
status: rt.union([rt.undefined, rt.number]),
attachmentId: rt.string,
})
),
});
export type FindCommentsQueryParams = rt.TypeOf<typeof FindCommentsQueryParamsRt>;
export type AttributesTypeActions = rt.TypeOf<typeof AttributesTypeActionsRt>;
export type AttributesTypeAlerts = rt.TypeOf<typeof AttributesTypeAlertsRt>;
export type AttributesTypeUser = rt.TypeOf<typeof AttributesTypeUserRt>;
export type AttributesTypeExternalReference = rt.TypeOf<typeof AttributesTypeExternalReferenceRt>;
export type AttributesTypeExternalReferenceSO = rt.TypeOf<
typeof AttributesTypeExternalReferenceSORt
>;
export type AttributesTypeExternalReferenceNoSO = rt.TypeOf<
typeof AttributesTypeExternalReferenceNoSORt
>;
export type ExternalReferenceWithoutRefs = rt.TypeOf<typeof ExternalReferenceWithoutRefsRt>;
export type AttributesTypePersistableState = rt.TypeOf<typeof AttributesTypePersistableStateRt>;
export type CommentAttributes = rt.TypeOf<typeof CommentAttributesRt>;
export type CommentAttributesNoSO = rt.TypeOf<typeof CommentAttributesNoSORt>;
export type CommentAttributesWithoutRefs = rt.TypeOf<typeof CommentAttributesWithoutRefsRt>;
export type CommentRequest = rt.TypeOf<typeof CommentRequestRt>;
export type BulkCreateCommentRequest = rt.TypeOf<typeof BulkCreateCommentRequestRt>;
export type Comment = rt.TypeOf<typeof CommentRt>;
export type CommentResponseUserType = rt.TypeOf<typeof CommentResponseTypeUserRt>;
export type CommentResponseAlertsType = rt.TypeOf<typeof CommentResponseTypeAlertsRt>;
export type CommentResponseTypePersistableState = rt.TypeOf<
typeof CommentResponseTypePersistableStateRt
>;
export type CommentResponseExternalReferenceType = rt.TypeOf<
typeof CommentResponseTypeExternalReferenceRt
>;
export type CommentResponseActionsType = rt.TypeOf<typeof CommentResponseTypeActionsRt>;
export type Comments = rt.TypeOf<typeof CommentsRt>;
export type CommentsFindResponse = rt.TypeOf<typeof CommentsFindResponseRt>;
export type CommentPatchRequest = rt.TypeOf<typeof CommentPatchRequestRt>;
export type CommentPatchAttributes = rt.TypeOf<typeof CommentPatchAttributesRt>;
export type CommentRequestUserType = rt.TypeOf<typeof ContextTypeUserRt>;
export type CommentRequestAlertType = rt.TypeOf<typeof AlertCommentRequestRt>;
export type CommentRequestActionsType = rt.TypeOf<typeof ActionsCommentRequestRt>;
export type CommentRequestExternalReferenceType = rt.TypeOf<typeof ExternalReferenceRt>;
export type CommentRequestExternalReferenceSOType = rt.TypeOf<typeof ExternalReferenceSORt>;
export type CommentRequestExternalReferenceNoSOType = rt.TypeOf<typeof ExternalReferenceNoSORt>;
export type CommentRequestPersistableStateType = rt.TypeOf<typeof PersistableStateAttachmentRt>;
export type BulkGetAttachmentsResponse = rt.TypeOf<typeof BulkGetAttachmentsResponseRt>;
export type BulkGetAttachmentsRequest = rt.TypeOf<typeof BulkGetAttachmentsRequestRt>;

View file

@ -1,36 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/**
* This field is used for authorization of the entities within the cases plugin. Each entity within Cases will have the owner field
* set to a string that represents the plugin that "owns" (i.e. the plugin that originally issued the POST request to
* create the entity) the entity.
*
* The Authorization class constructs a string composed of the operation being performed (createCase, getComment, etc),
* and the owner of the entity being acted upon or created. This string is then given to the Security plugin which
* checks to see if the user making the request has that particular string stored within it's privileges. If it does,
* then the operation succeeds, otherwise the operation fails.
*
* APIs that create/update an entity require that the owner field be passed in the body of the request.
* APIs that search for entities typically require that the owner be passed as a query parameter.
* APIs that specify an ID of an entity directly generally don't need to specify the owner field.
*
* For APIs that create/update an entity, the RBAC implementation checks to see if the user making the request has the
* correct privileges for performing that action (a create/update) for the specified owner.
* This check is done through the Security plugin's API.
*
* For APIs that search for entities, the RBAC implementation creates a filter for the saved objects query that limits
* the search to only owners that the user has access to. We also check that the objects returned by the saved objects
* API have the limited owner scope. If we find one that the user does not have permissions for, we throw a 403 error.
* The owner field that is passed in as a query parameter can be used to further limit the results. If a user attempts
* to pass an owner that they do not have access to, the owner is ignored.
*
* For APIs that retrieve/delete entities directly using their ID, the RBAC implementation requests the object first,
* and then checks to see if the user making the request has access to that operation and owner. If the user does, the
* operation continues, otherwise we throw a 403.
*/
export const OWNER_FIELD = 'owner';

View file

@ -1,62 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { CasesStatusRequestRt, CasesStatusResponseRt } from './status';
describe('status', () => {
describe('CasesStatusRequestRt', () => {
const defaultRequest = {
from: '2022-04-28T15:18:00.000Z',
to: '2022-04-28T15:22:00.000Z',
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = CasesStatusRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('has removes foo:bar attributes from request', () => {
const query = CasesStatusRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasesStatusResponseRt', () => {
const defaultResponse = {
count_closed_cases: 1,
count_in_progress_cases: 2,
count_open_cases: 1,
};
it('has expected attributes in response', () => {
const query = CasesStatusResponseRt.decode(defaultResponse);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultResponse,
});
});
it('removes foo:bar attributes from response', () => {
const query = CasesStatusResponseRt.decode({ ...defaultResponse, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultResponse,
});
});
});
});

View file

@ -1,124 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { PathReporter } from 'io-ts/lib/PathReporter';
import { MAX_SUGGESTED_PROFILES } from '../../constants';
import { SuggestUserProfilesRequestRt, CaseUserProfileRt } from './user_profiles';
describe('userProfile', () => {
describe('SuggestUserProfilesRequestRt', () => {
const defaultRequest = {
name: 'damaged_raccoon',
owners: ['cases'],
size: 5,
};
it('has expected attributes in request', () => {
const query = SuggestUserProfilesRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: {
name: 'damaged_raccoon',
owners: ['cases'],
size: 5,
},
});
});
it('has only name and owner in request', () => {
const query = SuggestUserProfilesRequestRt.decode({
name: 'damaged_raccoon',
owners: ['cases'],
foo: 'bar',
});
expect(query).toStrictEqual({
_tag: 'Right',
right: {
name: 'damaged_raccoon',
owners: ['cases'],
},
});
});
it('missing size parameter works correctly', () => {
const query = SuggestUserProfilesRequestRt.decode({
name: 'di maria',
owners: ['benfica'],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: {
name: 'di maria',
owners: ['benfica'],
},
});
});
it('removes foo:bar attributes from request', () => {
const query = SuggestUserProfilesRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: {
name: 'damaged_raccoon',
owners: ['cases'],
size: 5,
},
});
});
it(`does not accept size param bigger than ${MAX_SUGGESTED_PROFILES}`, () => {
const query = SuggestUserProfilesRequestRt.decode({
...defaultRequest,
size: MAX_SUGGESTED_PROFILES + 1,
});
expect(PathReporter.report(query)).toContain('The size field cannot be more than 10.');
});
it('does not accept size param lower than 1', () => {
const query = SuggestUserProfilesRequestRt.decode({
...defaultRequest,
size: 0,
});
expect(PathReporter.report(query)).toContain('The size field cannot be less than 1.');
});
});
describe('CaseUserProfileRt', () => {
it('has expected attributes in response', () => {
const query = CaseUserProfileRt.decode({
uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
});
expect(query).toStrictEqual({
_tag: 'Right',
right: {
uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
},
});
});
it('removes foo:bar attributes from response', () => {
const query = CaseUserProfileRt.decode({
uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
foo: 'bar',
});
expect(query).toStrictEqual({
_tag: 'Right',
right: {
uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
},
});
});
});
});

View file

@ -5,9 +5,7 @@
* 2.0.
*/
export * from './cases';
export * from './helpers';
export * from './runtime_types';
export * from './saved_object';
export * from './user';
export * from './metrics';

View file

@ -6,7 +6,6 @@
*/
import * as rt from 'io-ts';
import { BulkCreateCommentRequestRt, CommentType } from './cases';
import { decodeWithExcessOrThrow } from './runtime_types';
@ -101,15 +100,5 @@ describe('runtime_types', () => {
expect(decodeWithExcessOrThrow(schemaRt)({ a: 'hi' })).toStrictEqual({ a: 'hi' });
});
describe('BulkCreateCommentRequestRt', () => {
it('does not throw an error for BulkCreateCommentRequestRt', () => {
expect(() =>
decodeWithExcessOrThrow(BulkCreateCommentRequestRt)([
{ comment: 'hi', type: CommentType.user, owner: 'owner' },
])
).not.toThrow();
});
});
});
});

View file

@ -199,3 +199,33 @@ export const LOCAL_STORAGE_KEYS = {
*/
export const NONE_CONNECTOR_ID: string = 'none';
/**
* This field is used for authorization of the entities within the cases plugin. Each entity within Cases will have the owner field
* set to a string that represents the plugin that "owns" (i.e. the plugin that originally issued the POST request to
* create the entity) the entity.
*
* The Authorization class constructs a string composed of the operation being performed (createCase, getComment, etc),
* and the owner of the entity being acted upon or created. This string is then given to the Security plugin which
* checks to see if the user making the request has that particular string stored within it's privileges. If it does,
* then the operation succeeds, otherwise the operation fails.
*
* APIs that create/update an entity require that the owner field be passed in the body of the request.
* APIs that search for entities typically require that the owner be passed as a query parameter.
* APIs that specify an ID of an entity directly generally don't need to specify the owner field.
*
* For APIs that create/update an entity, the RBAC implementation checks to see if the user making the request has the
* correct privileges for performing that action (a create/update) for the specified owner.
* This check is done through the Security plugin's API.
*
* For APIs that search for entities, the RBAC implementation creates a filter for the saved objects query that limits
* the search to only owners that the user has access to. We also check that the objects returned by the saved objects
* API have the limited owner scope. If we find one that the user does not have permissions for, we throw a 403 error.
* The owner field that is passed in as a query parameter can be used to further limit the results. If a user attempts
* to pass an owner that they do not have access to, the owner is ignored.
*
* For APIs that retrieve/delete entities directly using their ID, the RBAC implementation requests the object first,
* and then checks to see if the user making the request has access to that operation and owner. If the user does, the
* operation continues, otherwise we throw a 403.
*/
export const OWNER_FIELD = 'owner';

View file

@ -15,7 +15,13 @@
// For example, constants below could eventually be in a "kbn-cases-constants" instead.
// See: https://docs.elastic.dev/kibana-dev-docs/key-concepts/platform-intro#public-plugin-api
export type { Case, Cases, CasesBulkGetResponse } from './api';
export type {
CasesBulkGetResponse,
CasePostRequest,
GetRelatedCasesByAlertResponse,
UserActionFindResponse,
} from './types/api';
export type { Case, Cases, RelatedCase } from './types/domain';
export type {
CaseUI,
CasesUI,
@ -23,8 +29,11 @@ export type {
Ecs,
CaseViewRefreshPropInterface,
CasesPermissions,
CasesStatus,
} from './ui/types';
export { CaseSeverity } from './types/domain';
export {
APP_ID,
CASES_URL,
@ -38,18 +47,14 @@ export {
UPDATE_CASES_CAPABILITY,
INTERNAL_BULK_GET_CASES_URL,
LENS_ATTACHMENT_TYPE,
INTERNAL_BULK_CREATE_ATTACHMENTS_URL,
SAVED_OBJECT_TYPES,
CASE_COMMENT_SAVED_OBJECT,
} from './constants';
export { ConnectorTypes } from './types/domain';
export {
getCasesFromAlertsUrl,
throwErrors,
CaseStatuses,
CaseSeverity,
CommentType,
ExternalReferenceStorageType,
} from './api';
export type { AttachmentAttributes } from './types/domain';
export { ConnectorTypes, AttachmentType, ExternalReferenceStorageType } from './types/domain';
export { getCasesFromAlertsUrl, getCaseFindUserActionsUrl, throwErrors } from './api';
export { StatusAll } from './ui/types';
export { createUICapabilities } from './utils/capabilities';
export { getApiTags } from './utils/api_tags';

View file

@ -5,10 +5,4 @@
* 2.0.
*/
export * from './case';
export * from './comment';
export * from './status';
export * from './constants';
export * from './alerts';
export * from './user_profiles';
export * from './assignee';
export * from './v1';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { AlertResponseRt } from './alerts';
import { AlertResponseRt } from './v1';
describe('Alerts', () => {
describe('AlertResponseRt', () => {

View file

@ -5,9 +5,4 @@
* 2.0.
*/
import * as rt from 'io-ts';
import { CaseUserProfileRt } from './user_profiles';
export const CaseAssigneesRt = rt.array(CaseUserProfileRt);
export type CaseAssignees = rt.TypeOf<typeof CaseAssigneesRt>;
export * from './v1';

View file

@ -0,0 +1,392 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { PathReporter } from 'io-ts/lib/PathReporter';
import { MAX_BULK_CREATE_ATTACHMENTS, MAX_COMMENT_LENGTH } from '../../../constants';
import { AttachmentType } from '../../domain/attachment/v1';
import {
AttachmentPatchRequestRt,
AttachmentRequestRt,
AttachmentsFindResponseRt,
BulkCreateAttachmentsRequestRt,
BulkDeleteFileAttachmentsRequestRt,
BulkGetAttachmentsRequestRt,
BulkGetAttachmentsResponseRt,
FindAttachmentsQueryParamsRt,
} from './v1';
describe('Attachments', () => {
describe('BulkDeleteFileAttachmentsRequestRt', () => {
it('has expected attributes in request', () => {
const query = BulkDeleteFileAttachmentsRequestRt.decode({ ids: ['abc', 'xyz'] });
expect(query).toStrictEqual({
_tag: 'Right',
right: { ids: ['abc', 'xyz'] },
});
});
it('removes foo:bar attributes from request', () => {
const query = BulkDeleteFileAttachmentsRequestRt.decode({
ids: ['abc', 'xyz'],
foo: 'bar',
});
expect(query).toStrictEqual({
_tag: 'Right',
right: { ids: ['abc', 'xyz'] },
});
});
});
describe('AttachmentRequestRt', () => {
const defaultRequest = {
comment: 'Solve this fast!',
type: AttachmentType.user,
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = AttachmentRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = AttachmentRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
describe('errors', () => {
describe('commentType: user', () => {
it('throws error when comment is too long', () => {
const longComment = 'x'.repeat(MAX_COMMENT_LENGTH + 1);
expect(
PathReporter.report(
AttachmentRequestRt.decode({ ...defaultRequest, comment: longComment })
)
).toContain('The length of the comment is too long. The maximum length is 30000.');
});
it('throws error when comment is empty', () => {
expect(
PathReporter.report(AttachmentRequestRt.decode({ ...defaultRequest, comment: '' }))
).toContain('The comment field cannot be an empty string.');
});
it('throws error when comment string of empty characters', () => {
expect(
PathReporter.report(AttachmentRequestRt.decode({ ...defaultRequest, comment: ' ' }))
).toContain('The comment field cannot be an empty string.');
});
});
describe('commentType: action', () => {
const request = {
type: AttachmentType.actions,
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
},
],
type: 'isolate',
},
owner: 'cases',
};
it('throws error when comment is too long', () => {
const longComment = 'x'.repeat(MAX_COMMENT_LENGTH + 1);
expect(
PathReporter.report(AttachmentRequestRt.decode({ ...request, comment: longComment }))
).toContain('The length of the comment is too long. The maximum length is 30000.');
});
it('throws error when comment is empty', () => {
expect(
PathReporter.report(AttachmentRequestRt.decode({ ...request, comment: '' }))
).toContain('The comment field cannot be an empty string.');
});
it('throws error when comment string of empty characters', () => {
expect(
PathReporter.report(AttachmentRequestRt.decode({ ...request, comment: ' ' }))
).toContain('The comment field cannot be an empty string.');
});
});
});
});
describe('AttachmentPatchRequestRt', () => {
const defaultRequest = {
alertId: 'alert-id-1',
index: 'alert-index-1',
type: AttachmentType.alert,
id: 'alert-comment-id',
owner: 'cases',
rule: {
id: 'rule-id-1',
name: 'Awesome rule',
},
version: 'WzQ3LDFc',
};
it('has expected attributes in request', () => {
const query = AttachmentPatchRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = AttachmentPatchRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('AttachmentsFindResponseRt', () => {
const defaultRequest = {
comments: [
{
comment: 'Solve this fast!',
type: AttachmentType.user,
owner: 'cases',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
id: 'basic-comment-id',
version: 'WzQ3LDFc',
},
],
page: 1,
per_page: 10,
total: 1,
};
it('has expected attributes in request', () => {
const query = AttachmentsFindResponseRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = AttachmentsFindResponseRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from comments', () => {
const query = AttachmentsFindResponseRt.decode({
...defaultRequest,
comments: [{ ...defaultRequest.comments[0], foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('FindAttachmentsQueryParamsRt', () => {
const defaultRequest = {
page: 1,
perPage: 10,
sortOrder: 'asc',
};
it('has expected attributes in request', () => {
const query = FindAttachmentsQueryParamsRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = FindAttachmentsQueryParamsRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('BulkCreateAttachmentsRequestRt', () => {
const defaultRequest = [
{
comment: 'Solve this fast!',
type: AttachmentType.user,
owner: 'cases',
},
];
it('has expected attributes in request', () => {
const query = BulkCreateAttachmentsRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = BulkCreateAttachmentsRequestRt.decode([
{ comment: 'Solve this fast!', type: AttachmentType.user, owner: 'cases', foo: 'bar' },
]);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
describe('errors', () => {
it(`throws error when attachments are more than ${MAX_BULK_CREATE_ATTACHMENTS}`, () => {
const comment = {
comment: 'Solve this fast!',
type: AttachmentType.user,
owner: 'cases',
};
const attachments = Array(MAX_BULK_CREATE_ATTACHMENTS + 1).fill(comment);
expect(PathReporter.report(BulkCreateAttachmentsRequestRt.decode(attachments))).toContain(
`The length of the field attachments is too long. Array must be of length <= ${MAX_BULK_CREATE_ATTACHMENTS}.`
);
});
it(`no errors when empty array of attachments`, () => {
expect(PathReporter.report(BulkCreateAttachmentsRequestRt.decode([]))).toStrictEqual([
'No errors!',
]);
});
});
});
describe('BulkGetAttachmentsRequestRt', () => {
it('has expected attributes in request', () => {
const query = BulkGetAttachmentsRequestRt.decode({ ids: ['abc', 'xyz'] });
expect(query).toStrictEqual({
_tag: 'Right',
right: { ids: ['abc', 'xyz'] },
});
});
it('removes foo:bar attributes from request', () => {
const query = BulkGetAttachmentsRequestRt.decode({ ids: ['abc', 'xyz'], foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: { ids: ['abc', 'xyz'] },
});
});
});
describe('BulkGetAttachmentsResponseRt', () => {
const defaultRequest = {
attachments: [
{
comment: 'Solve this fast!',
type: AttachmentType.user,
owner: 'cases',
id: 'basic-comment-id',
version: 'WzQ3LDFc',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
},
],
errors: [
{
error: 'error',
message: 'not found',
status: 404,
attachmentId: 'abc',
},
],
};
it('has expected attributes in request', () => {
const query = BulkGetAttachmentsResponseRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = BulkGetAttachmentsResponseRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from attachments', () => {
const query = BulkGetAttachmentsResponseRt.decode({
...defaultRequest,
attachments: [{ ...defaultRequest.attachments[0], foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from errors', () => {
const query = BulkGetAttachmentsResponseRt.decode({
...defaultRequest,
errors: [{ ...defaultRequest.errors[0], foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
});

View file

@ -0,0 +1,157 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import * as rt from 'io-ts';
import {
MAX_BULK_CREATE_ATTACHMENTS,
MAX_BULK_GET_ATTACHMENTS,
MAX_COMMENTS_PER_PAGE,
MAX_COMMENT_LENGTH,
MAX_DELETE_FILES,
} from '../../../constants';
import {
limitedArraySchema,
limitedStringSchema,
NonEmptyString,
paginationSchema,
} from '../../../schema';
import {
UserCommentAttachmentPayloadRt,
AlertAttachmentPayloadRt,
ActionsAttachmentPayloadRt,
ExternalReferenceNoSOAttachmentPayloadRt,
ExternalReferenceSOAttachmentPayloadRt,
ExternalReferenceSOWithoutRefsAttachmentPayloadRt,
PersistableStateAttachmentPayloadRt,
AttachmentType,
AttachmentRt,
AttachmentsRt,
} from '../../domain/attachment/v1';
/**
* Files
*/
const MIN_DELETE_IDS = 1;
export const BulkDeleteFileAttachmentsRequestRt = rt.strict({
ids: limitedArraySchema({
codec: NonEmptyString,
min: MIN_DELETE_IDS,
max: MAX_DELETE_FILES,
fieldName: 'ids',
}),
});
export type BulkDeleteFileAttachmentsRequest = rt.TypeOf<typeof BulkDeleteFileAttachmentsRequestRt>;
const BasicAttachmentRequestRt = rt.union([
UserCommentAttachmentPayloadRt,
AlertAttachmentPayloadRt,
ActionsAttachmentPayloadRt,
ExternalReferenceNoSOAttachmentPayloadRt,
PersistableStateAttachmentPayloadRt,
]);
export const AttachmentRequestRt = rt.union([
rt.strict({
comment: limitedStringSchema({ fieldName: 'comment', min: 1, max: MAX_COMMENT_LENGTH }),
type: rt.literal(AttachmentType.user),
owner: rt.string,
}),
AlertAttachmentPayloadRt,
rt.strict({
type: rt.literal(AttachmentType.actions),
comment: limitedStringSchema({ fieldName: 'comment', min: 1, max: MAX_COMMENT_LENGTH }),
actions: rt.strict({
targets: rt.array(
rt.strict({
hostname: rt.string,
endpointId: rt.string,
})
),
type: rt.string,
}),
owner: rt.string,
}),
ExternalReferenceNoSOAttachmentPayloadRt,
ExternalReferenceSOAttachmentPayloadRt,
PersistableStateAttachmentPayloadRt,
]);
export const AttachmentRequestWithoutRefsRt = rt.union([
BasicAttachmentRequestRt,
ExternalReferenceSOWithoutRefsAttachmentPayloadRt,
]);
export const AttachmentPatchRequestRt = rt.intersection([
/**
* Partial updates are not allowed.
* We want to prevent the user for changing the type without removing invalid fields.
*
* injectAttachmentSOAttributesFromRefsForPatch is dependent on this assumption.
* The consumers of the persistable attachment service should always get the
* persistableStateAttachmentState on a patch.
*/
AttachmentRequestRt,
rt.strict({ id: rt.string, version: rt.string }),
]);
export const AttachmentsFindResponseRt = rt.strict({
comments: rt.array(AttachmentRt),
page: rt.number,
per_page: rt.number,
total: rt.number,
});
export const FindAttachmentsQueryParamsRt = rt.intersection([
rt.exact(
rt.partial({
/**
* Order to sort the response
*/
sortOrder: rt.union([rt.literal('desc'), rt.literal('asc')]),
})
),
paginationSchema({ maxPerPage: MAX_COMMENTS_PER_PAGE }),
]);
export const BulkCreateAttachmentsRequestRt = limitedArraySchema({
codec: AttachmentRequestRt,
min: 0,
max: MAX_BULK_CREATE_ATTACHMENTS,
fieldName: 'attachments',
});
export const BulkGetAttachmentsRequestRt = rt.strict({
ids: limitedArraySchema({
codec: rt.string,
min: 1,
max: MAX_BULK_GET_ATTACHMENTS,
fieldName: 'ids',
}),
});
export const BulkGetAttachmentsResponseRt = rt.strict({
attachments: AttachmentsRt,
errors: rt.array(
rt.strict({
error: rt.string,
message: rt.string,
status: rt.union([rt.undefined, rt.number]),
attachmentId: rt.string,
})
),
});
export type FindAttachmentsQueryParams = rt.TypeOf<typeof FindAttachmentsQueryParamsRt>;
export type AttachmentsFindResponse = rt.TypeOf<typeof AttachmentsFindResponseRt>;
export type AttachmentRequest = rt.TypeOf<typeof AttachmentRequestRt>;
export type AttachmentPatchRequest = rt.TypeOf<typeof AttachmentPatchRequestRt>;
export type BulkCreateAttachmentsRequest = rt.TypeOf<typeof BulkCreateAttachmentsRequestRt>;
export type BulkGetAttachmentsResponse = rt.TypeOf<typeof BulkGetAttachmentsResponseRt>;
export type BulkGetAttachmentsRequest = rt.TypeOf<typeof BulkGetAttachmentsRequestRt>;

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './v1';

View file

@ -0,0 +1,555 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { PathReporter } from 'io-ts/lib/PathReporter';
import { AttachmentType } from '../../domain/attachment/v1';
import { CaseSeverity, CaseStatuses } from '../../domain/case/v1';
import { ConnectorTypes } from '../../domain/connector/v1';
import { CasesStatusRequestRt, CasesStatusResponseRt } from '../stats/v1';
import {
AllReportersFindRequestRt,
CasePatchRequestRt,
CasePostRequestRt,
CasePushRequestParamsRt,
CaseResolveResponseRt,
CasesBulkGetRequestRt,
CasesBulkGetResponseRt,
CasesByAlertIDRequestRt,
CasesFindRequestRt,
CasesFindRequestSearchFieldsRt,
CasesFindRequestSortFieldsRt,
CasesFindResponseRt,
CasesPatchRequestRt,
} from './v1';
const basicCase = {
owner: 'cases',
closed_at: null,
closed_by: null,
id: 'basic-case-id',
comments: [
{
comment: 'Solve this fast!',
type: AttachmentType.user,
id: 'basic-comment-id',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
owner: 'cases',
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
version: 'WzQ3LDFc',
},
],
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
connector: {
id: 'none',
name: 'My Connector',
type: ConnectorTypes.none,
fields: null,
},
description: 'Security banana Issue',
severity: CaseSeverity.LOW,
duration: null,
external_service: null,
status: CaseStatuses.open,
tags: ['coke', 'pepsi'],
title: 'Another horrible breach!!',
totalComment: 1,
totalAlerts: 0,
updated_at: '2020-02-20T15:02:57.995Z',
updated_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
version: 'WzQ3LDFd',
settings: {
syncAlerts: true,
},
// damaged_raccoon uid
assignees: [{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }],
category: null,
};
describe('Status', () => {
describe('CasesStatusRequestRt', () => {
const defaultRequest = {
from: '2022-04-28T15:18:00.000Z',
to: '2022-04-28T15:22:00.000Z',
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = CasesStatusRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('has removes foo:bar attributes from request', () => {
const query = CasesStatusRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasesStatusResponseRt', () => {
const defaultResponse = {
count_closed_cases: 1,
count_in_progress_cases: 2,
count_open_cases: 1,
};
it('has expected attributes in response', () => {
const query = CasesStatusResponseRt.decode(defaultResponse);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultResponse,
});
});
it('removes foo:bar attributes from response', () => {
const query = CasesStatusResponseRt.decode({ ...defaultResponse, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultResponse,
});
});
});
describe('CasePostRequestRt', () => {
const defaultRequest = {
description: 'A description',
tags: ['new', 'case'],
title: 'My new case',
connector: {
id: '123',
name: 'My connector',
type: ConnectorTypes.jira,
fields: { issueType: 'Task', priority: 'High', parent: null },
},
settings: {
syncAlerts: true,
},
owner: 'cases',
severity: CaseSeverity.LOW,
assignees: [{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }],
};
it('has expected attributes in request', () => {
const query = CasePostRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasePostRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from connector', () => {
const query = CasePostRequestRt.decode({
...defaultRequest,
connector: { ...defaultRequest.connector, foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasesFindRequestRt', () => {
const defaultRequest = {
tags: ['new', 'case'],
status: CaseStatuses.open,
severity: CaseSeverity.LOW,
assignees: ['damaged_racoon'],
reporters: ['damaged_racoon'],
defaultSearchOperator: 'AND',
from: 'now',
page: '1',
perPage: '10',
search: 'search text',
searchFields: ['title', 'description'],
to: '1w',
sortOrder: 'desc',
sortField: 'createdAt',
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = CasesFindRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: { ...defaultRequest, page: 1, perPage: 10 },
});
});
it('removes foo:bar attributes from request', () => {
const query = CasesFindRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: { ...defaultRequest, page: 1, perPage: 10 },
});
});
const searchFields = Object.keys(CasesFindRequestSearchFieldsRt.keys);
it.each(searchFields)('succeeds with %s as searchFields', (field) => {
const query = CasesFindRequestRt.decode({ ...defaultRequest, searchFields: field });
expect(query).toStrictEqual({
_tag: 'Right',
right: { ...defaultRequest, searchFields: field, page: 1, perPage: 10 },
});
});
const sortFields = Object.keys(CasesFindRequestSortFieldsRt.keys);
it.each(sortFields)('succeeds with %s as sortField', (sortField) => {
const query = CasesFindRequestRt.decode({ ...defaultRequest, sortField });
expect(query).toStrictEqual({
_tag: 'Right',
right: { ...defaultRequest, sortField, page: 1, perPage: 10 },
});
});
it('removes rootSearchField when passed', () => {
expect(
PathReporter.report(
CasesFindRequestRt.decode({ ...defaultRequest, rootSearchField: ['foobar'] })
)
).toContain('No errors!');
});
describe('errors', () => {
it('throws error when invalid searchField passed', () => {
expect(
PathReporter.report(
CasesFindRequestRt.decode({ ...defaultRequest, searchFields: 'foobar' })
)
).not.toContain('No errors!');
});
it('throws error when invalid sortField passed', () => {
expect(
PathReporter.report(CasesFindRequestRt.decode({ ...defaultRequest, sortField: 'foobar' }))
).not.toContain('No errors!');
});
it('succeeds when valid parameters passed', () => {
expect(PathReporter.report(CasesFindRequestRt.decode(defaultRequest))).toContain(
'No errors!'
);
});
});
});
});
describe('CasesByAlertIDRequestRt', () => {
it('has expected attributes in request', () => {
const query = CasesByAlertIDRequestRt.decode({ owner: 'cases' });
expect(query).toStrictEqual({
_tag: 'Right',
right: { owner: 'cases' },
});
});
it('removes foo:bar attributes from request', () => {
const query = CasesByAlertIDRequestRt.decode({ owner: ['cases'], foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: { owner: ['cases'] },
});
});
});
describe('CaseResolveResponseRt', () => {
const defaultRequest = {
case: { ...basicCase },
outcome: 'exactMatch',
alias_target_id: 'sample-target-id',
alias_purpose: 'savedObjectConversion',
};
it('has expected attributes in request', () => {
const query = CaseResolveResponseRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CaseResolveResponseRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasesFindResponseRt', () => {
const defaultRequest = {
cases: [{ ...basicCase }],
page: 1,
per_page: 10,
total: 20,
count_open_cases: 10,
count_in_progress_cases: 5,
count_closed_cases: 5,
};
it('has expected attributes in request', () => {
const query = CasesFindResponseRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasesFindResponseRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from cases', () => {
const query = CasesFindResponseRt.decode({
...defaultRequest,
cases: [{ ...basicCase, foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasePatchRequestRt', () => {
const defaultRequest = {
id: 'basic-case-id',
version: 'WzQ3LDFd',
description: 'Updated description',
};
it('has expected attributes in request', () => {
const query = CasePatchRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasePatchRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasesPatchRequestRt', () => {
const defaultRequest = {
cases: [
{
id: 'basic-case-id',
version: 'WzQ3LDFd',
description: 'Updated description',
},
],
};
it('has expected attributes in request', () => {
const query = CasesPatchRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasesPatchRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasePushRequestParamsRt', () => {
const defaultRequest = {
case_id: 'basic-case-id',
connector_id: 'basic-connector-id',
};
it('has expected attributes in request', () => {
const query = CasePushRequestParamsRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasePushRequestParamsRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('AllReportersFindRequestRt', () => {
const defaultRequest = {
owner: ['cases', 'security-solution'],
};
it('has expected attributes in request', () => {
const query = AllReportersFindRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = AllReportersFindRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasesBulkGetRequestRt', () => {
const defaultRequest = {
ids: ['case-1', 'case-2'],
};
it('has expected attributes in request', () => {
const query = CasesBulkGetRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasesBulkGetRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasesBulkGetResponseRt', () => {
const defaultRequest = {
cases: [basicCase],
errors: [
{
error: 'error',
message: 'error-message',
status: 403,
caseId: 'basic-case-id',
},
],
};
it('has expected attributes in request', () => {
const query = CasesBulkGetResponseRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasesBulkGetResponseRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from cases', () => {
const query = CasesBulkGetResponseRt.decode({
...defaultRequest,
cases: [{ ...defaultRequest.cases[0], foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: { ...defaultRequest, cases: defaultRequest.cases },
});
});
it('removes foo:bar attributes from errors', () => {
const query = CasesBulkGetResponseRt.decode({
...defaultRequest,
errors: [{ ...defaultRequest.errors[0], foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});

View file

@ -6,145 +6,41 @@
*/
import * as rt from 'io-ts';
import { UserRt } from '../user';
import { CommentRt } from './comment';
import { CasesStatusResponseRt, CaseStatusRt } from './status';
import { CaseAssigneesRt } from './assignee';
import {
limitedArraySchema,
limitedStringSchema,
NonEmptyString,
paginationSchema,
} from '../../schema';
import {
MAX_DELETE_IDS_LENGTH,
MAX_DESCRIPTION_LENGTH,
MAX_TITLE_LENGTH,
MAX_LENGTH_PER_TAG,
MAX_CATEGORY_LENGTH,
MAX_TAGS_PER_CASE,
MAX_TITLE_LENGTH,
MAX_CATEGORY_LENGTH,
MAX_ASSIGNEES_FILTER_LENGTH,
MAX_CASES_PER_PAGE,
MAX_DELETE_IDS_LENGTH,
MAX_REPORTERS_FILTER_LENGTH,
MAX_TAGS_FILTER_LENGTH,
MAX_CASES_TO_UPDATE,
MAX_BULK_GET_CASES,
MAX_CASES_PER_PAGE,
} from '../../constants';
import { CaseConnectorRt } from '../../types/domain/connector/v1';
export const AttachmentTotalsRt = rt.strict({
alerts: rt.number,
userComments: rt.number,
});
export const RelatedCaseInfoRt = rt.strict({
id: rt.string,
title: rt.string,
description: rt.string,
status: CaseStatusRt,
createdAt: rt.string,
totals: AttachmentTotalsRt,
});
export const CasesByAlertIdRt = rt.array(RelatedCaseInfoRt);
export const SettingsRt = rt.strict({
syncAlerts: rt.boolean,
});
export enum CaseSeverity {
LOW = 'low',
MEDIUM = 'medium',
HIGH = 'high',
CRITICAL = 'critical',
}
export const CaseSeverityRt = rt.union([
rt.literal(CaseSeverity.LOW),
rt.literal(CaseSeverity.MEDIUM),
rt.literal(CaseSeverity.HIGH),
rt.literal(CaseSeverity.CRITICAL),
]);
const CaseBasicRt = rt.strict({
/**
* The description of the case
*/
description: rt.string,
/**
* The current status of the case (open, closed, in-progress)
*/
status: CaseStatusRt,
/**
* The identifying strings for filter a case
*/
tags: rt.array(rt.string),
/**
* The title of a case
*/
title: rt.string,
/**
* The external system that the case can be synced with
*/
connector: CaseConnectorRt,
/**
* The alert sync settings
*/
settings: SettingsRt,
/**
* The plugin owner of the case
*/
owner: rt.string,
/**
* The severity of the case
*/
severity: CaseSeverityRt,
/**
* The users assigned to this case
*/
assignees: CaseAssigneesRt,
/**
* The category of the case.
*/
category: rt.union([rt.string, rt.null]),
});
} from '../../../constants';
import {
limitedStringSchema,
limitedArraySchema,
NonEmptyString,
paginationSchema,
} from '../../../schema';
import {
CaseRt,
CaseSettingsRt,
CaseSeverityRt,
CasesRt,
CaseStatusRt,
RelatedCaseRt,
} from '../../domain/case/v1';
import { CaseConnectorRt } from '../../domain/connector/v1';
import { CaseAssigneesRt, UserRt } from '../../domain/user/v1';
import { CasesStatusResponseRt } from '../stats/v1';
/**
* This represents the push to service UserAction. It lacks the connector_id because that is stored in a different field
* within the user action object in the API response.
* Create case
*/
export const CaseUserActionExternalServiceRt = rt.strict({
connector_name: rt.string,
external_id: rt.string,
external_title: rt.string,
external_url: rt.string,
pushed_at: rt.string,
pushed_by: UserRt,
});
export const CaseExternalServiceBasicRt = rt.intersection([
rt.strict({
connector_id: rt.string,
}),
CaseUserActionExternalServiceRt,
]);
export const CaseFullExternalServiceRt = rt.union([CaseExternalServiceBasicRt, rt.null]);
export const CaseAttributesRt = rt.intersection([
CaseBasicRt,
rt.strict({
duration: rt.union([rt.number, rt.null]),
closed_at: rt.union([rt.string, rt.null]),
closed_by: rt.union([UserRt, rt.null]),
created_at: rt.string,
created_by: UserRt,
external_service: CaseFullExternalServiceRt,
updated_at: rt.union([rt.string, rt.null]),
updated_by: rt.union([UserRt, rt.null]),
}),
]);
export const CasePostRequestRt = rt.intersection([
rt.strict({
@ -176,7 +72,7 @@ export const CasePostRequestRt = rt.intersection([
/**
* Sync settings for alerts
*/
settings: SettingsRt,
settings: CaseSettingsRt,
/**
* The owner here must match the string used when a plugin registers a feature with access to the cases plugin. The user
* creating this case must also be granted access to that plugin's feature.
@ -324,6 +220,20 @@ export const CasesFindRequestRt = rt.intersection([
paginationSchema({ maxPerPage: MAX_CASES_PER_PAGE }),
]);
export const CasesFindResponseRt = rt.intersection([
rt.strict({
cases: rt.array(CaseRt),
page: rt.number,
per_page: rt.number,
total: rt.number,
}),
CasesStatusResponseRt,
]);
/**
* Delete cases
*/
export const CasesDeleteRequestRt = limitedArraySchema({
codec: NonEmptyString,
min: 1,
@ -331,30 +241,9 @@ export const CasesDeleteRequestRt = limitedArraySchema({
fieldName: 'ids',
});
export const CasesByAlertIDRequestRt = rt.exact(
rt.partial({
/**
* The type of cases to retrieve given an alert ID. If no owner is provided, all cases
* that the user has access to will be returned.
*/
owner: rt.union([rt.array(rt.string), rt.string]),
})
);
export const CaseRt = rt.intersection([
CaseAttributesRt,
rt.strict({
id: rt.string,
totalComment: rt.number,
totalAlerts: rt.number,
version: rt.string,
}),
rt.exact(
rt.partial({
comments: rt.array(CommentRt),
})
),
]);
/**
* Resolve case
*/
export const CaseResolveResponseRt = rt.intersection([
rt.strict({
@ -372,16 +261,28 @@ export const CaseResolveResponseRt = rt.intersection([
),
]);
export const CasesFindResponseRt = rt.intersection([
rt.strict({
cases: rt.array(CaseRt),
page: rt.number,
per_page: rt.number,
total: rt.number,
}),
CasesStatusResponseRt,
]);
/**
* Get cases
*/
export const CasesBulkGetRequestRt = rt.strict({
ids: limitedArraySchema({ codec: rt.string, min: 1, max: MAX_BULK_GET_CASES, fieldName: 'ids' }),
});
export const CasesBulkGetResponseRt = rt.strict({
cases: CasesRt,
errors: rt.array(
rt.strict({
error: rt.string,
message: rt.string,
status: rt.union([rt.undefined, rt.number]),
caseId: rt.string,
})
),
});
/**
* Update cases
*/
export const CasePatchRequestRt = rt.intersection([
rt.exact(
rt.partial({
@ -417,7 +318,7 @@ export const CasePatchRequestRt = rt.intersection([
/**
* The alert sync settings
*/
settings: SettingsRt,
settings: CaseSettingsRt,
/**
* The plugin owner of the case
*/
@ -454,34 +355,18 @@ export const CasesPatchRequestRt = rt.strict({
}),
});
export const CasesRt = rt.array(CaseRt);
/**
* Push case
*/
export const CasePushRequestParamsRt = rt.strict({
case_id: rt.string,
connector_id: rt.string,
});
export const ExternalServiceResponseRt = rt.intersection([
rt.strict({
title: rt.string,
id: rt.string,
pushedDate: rt.string,
url: rt.string,
}),
rt.exact(
rt.partial({
comments: rt.array(
rt.intersection([
rt.strict({
commentId: rt.string,
pushedDate: rt.string,
}),
rt.exact(rt.partial({ externalCommentId: rt.string })),
])
),
})
),
]);
/**
* Taxonomies
*/
export const AllTagsFindRequestRt = rt.exact(
rt.partial({
@ -509,28 +394,24 @@ export const GetTagsResponseRt = rt.array(rt.string);
export const GetCategoriesResponseRt = rt.array(rt.string);
export const GetReportersResponseRt = rt.array(UserRt);
export const CasesBulkGetRequestRt = rt.strict({
ids: limitedArraySchema({ codec: rt.string, min: 1, max: MAX_BULK_GET_CASES, fieldName: 'ids' }),
});
/**
* Alerts
*/
export const CasesBulkGetResponseRt = rt.strict({
cases: CasesRt,
errors: rt.array(
rt.strict({
error: rt.string,
message: rt.string,
status: rt.union([rt.undefined, rt.number]),
caseId: rt.string,
})
),
});
export const CasesByAlertIDRequestRt = rt.exact(
rt.partial({
/**
* The type of cases to retrieve given an alert ID. If no owner is provided, all cases
* that the user has access to will be returned.
*/
owner: rt.union([rt.array(rt.string), rt.string]),
})
);
export type CaseAttributes = rt.TypeOf<typeof CaseAttributesRt>;
export const GetRelatedCasesByAlertResponseRt = rt.array(RelatedCaseRt);
export type CasePostRequest = rt.TypeOf<typeof CasePostRequestRt>;
export type Case = rt.TypeOf<typeof CaseRt>;
export type CaseResolveResponse = rt.TypeOf<typeof CaseResolveResponseRt>;
export type Cases = rt.TypeOf<typeof CasesRt>;
export type CasesDeleteRequest = rt.TypeOf<typeof CasesDeleteRequestRt>;
export type CasesByAlertIDRequest = rt.TypeOf<typeof CasesByAlertIDRequestRt>;
export type CasesFindRequest = rt.TypeOf<typeof CasesFindRequestRt>;
@ -538,18 +419,12 @@ export type CasesFindRequestSortFields = rt.TypeOf<typeof CasesFindRequestSortFi
export type CasesFindResponse = rt.TypeOf<typeof CasesFindResponseRt>;
export type CasePatchRequest = rt.TypeOf<typeof CasePatchRequestRt>;
export type CasesPatchRequest = rt.TypeOf<typeof CasesPatchRequestRt>;
export type CaseFullExternalService = rt.TypeOf<typeof CaseFullExternalServiceRt>;
export type CaseSettings = rt.TypeOf<typeof SettingsRt>;
export type ExternalServiceResponse = rt.TypeOf<typeof ExternalServiceResponseRt>;
export type CaseExternalServiceBasic = rt.TypeOf<typeof CaseExternalServiceBasicRt>;
export type AllTagsFindRequest = rt.TypeOf<typeof AllTagsFindRequestRt>;
export type GetTagsResponse = rt.TypeOf<typeof GetTagsResponseRt>;
export type AllCategoriesFindRequest = rt.TypeOf<typeof AllCategoriesFindRequestRt>;
export type GetCategoriesResponse = rt.TypeOf<typeof GetCategoriesResponseRt>;
export type AllReportersFindRequest = AllTagsFindRequest;
export type AttachmentTotals = rt.TypeOf<typeof AttachmentTotalsRt>;
export type RelatedCaseInfo = rt.TypeOf<typeof RelatedCaseInfoRt>;
export type CasesByAlertId = rt.TypeOf<typeof CasesByAlertIdRt>;
export type GetReportersResponse = rt.TypeOf<typeof GetReportersResponseRt>;
export type CasesBulkGetRequest = rt.TypeOf<typeof CasesBulkGetRequestRt>;
export type CasesBulkGetResponse = rt.TypeOf<typeof CasesBulkGetResponseRt>;
export type GetRelatedCasesByAlertResponse = rt.TypeOf<typeof GetRelatedCasesByAlertResponseRt>;

View file

@ -6,28 +6,13 @@
*/
import * as rt from 'io-ts';
import { CaseExternalServiceBasicRt } from '../../../api';
import { ExternalServiceRt } from '../../domain/external_service/v1';
import { CaseConnectorRt, ConnectorMappingsRt } from '../../domain/connector/v1';
const ActionConnectorResultRt = rt.intersection([
rt.strict({
id: rt.string,
actionTypeId: rt.string,
name: rt.string,
isDeprecated: rt.boolean,
isPreconfigured: rt.boolean,
isSystemAction: rt.boolean,
referencedByCount: rt.number,
}),
rt.exact(rt.partial({ config: rt.record(rt.string, rt.unknown), isMissingSecrets: rt.boolean })),
]);
export const FindActionConnectorResponseRt = rt.array(ActionConnectorResultRt);
const PushDetailsRt = rt.strict({
latestUserActionPushDate: rt.string,
oldestUserActionPushDate: rt.string,
externalService: CaseExternalServiceBasicRt,
externalService: ExternalServiceRt,
});
const CaseConnectorPushInfoRt = rt.intersection([
@ -52,6 +37,21 @@ export const GetCaseConnectorsResponseRt = rt.record(
])
);
const ActionConnectorResultRt = rt.intersection([
rt.strict({
id: rt.string,
actionTypeId: rt.string,
name: rt.string,
isDeprecated: rt.boolean,
isPreconfigured: rt.boolean,
isSystemAction: rt.boolean,
referencedByCount: rt.number,
}),
rt.exact(rt.partial({ config: rt.record(rt.string, rt.unknown), isMissingSecrets: rt.boolean })),
]);
export const FindActionConnectorResponseRt = rt.array(ActionConnectorResultRt);
export const ConnectorMappingResponseRt = rt.strict({
id: rt.string,
version: rt.string,

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './v1';

View file

@ -0,0 +1,54 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ExternalServiceResponseRt } from './v1';
describe('ExternalServiceResponseRt', () => {
const defaultRequest = {
title: 'case_title',
id: 'basic-case-id',
pushedDate: '2020-02-19T23:06:33.798Z',
url: 'https://atlassian.com',
comments: [
{
commentId: 'basic-comment-id',
pushedDate: '2020-02-19T23:06:33.798Z',
externalCommentId: 'external-comment-id',
},
],
};
it('has expected attributes in request', () => {
const query = ExternalServiceResponseRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = ExternalServiceResponseRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from comments', () => {
const query = ExternalServiceResponseRt.decode({
...defaultRequest,
comments: [{ ...defaultRequest.comments[0], foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});

View file

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import * as rt from 'io-ts';
export const ExternalServiceResponseRt = rt.intersection([
rt.strict({
title: rt.string,
id: rt.string,
pushedDate: rt.string,
url: rt.string,
}),
rt.exact(
rt.partial({
comments: rt.array(
rt.intersection([
rt.strict({
commentId: rt.string,
pushedDate: rt.string,
}),
rt.exact(rt.partial({ externalCommentId: rt.string })),
])
),
})
),
]);
export type ExternalServiceResponse = rt.TypeOf<typeof ExternalServiceResponseRt>;

View file

@ -8,9 +8,21 @@
// Latest
export * from './configure/latest';
export * from './user_action/latest';
export * from './alert/latest';
export * from './case/latest';
export * from './external_service/latest';
export * from './stats/latest';
export * from './user/latest';
export * from './connector/latest';
export * from './attachment/latest';
// V1
export * as configureApiV1 from './configure/v1';
export * as userActionApiV1 from './user_action/v1';
export * as alertApiV1 from './alert/v1';
export * as statsApiV1 from './stats/v1';
export * as caseApiV1 from './case/v1';
export * as externalServiceApiV1 from './external_service/v1';
export * as userApiV1 from './user/v1';
export * as connectorApiV1 from './connector/v1';
export * as attachmentApiV1 from './attachment/v1';

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './v1';

View file

@ -6,17 +6,6 @@
*/
import * as rt from 'io-ts';
import { CaseStatuses } from '@kbn/cases-components/src/status/types';
export { CaseStatuses };
export const CaseStatusRt = rt.union([
rt.literal(CaseStatuses.open),
rt.literal(CaseStatuses['in-progress']),
rt.literal(CaseStatuses.closed),
]);
export const caseStatuses = Object.values(CaseStatuses);
export const CasesStatusResponseRt = rt.strict({
count_open_cases: rt.number,

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './v1';

View file

@ -5,137 +5,11 @@
* 2.0.
*/
import { set } from 'lodash';
import { UserRt, UserWithProfileInfoRt, UsersRt, GetCaseUsersResponseRt } from './user';
import { MAX_SUGGESTED_PROFILES } from '../../../constants';
import { PathReporter } from 'io-ts/lib/PathReporter';
import { GetCaseUsersResponseRt, SuggestUserProfilesRequestRt } from './v1';
describe('User', () => {
describe('UserRt', () => {
const defaultRequest = {
full_name: 'elastic',
email: 'testemail@elastic.co',
username: 'elastic',
profile_uid: 'profile-uid-1',
};
it('has expected attributes in request', () => {
const query = UserRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = UserRt.decode({
...defaultRequest,
foo: 'bar',
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('UserWithProfileInfoRt', () => {
const defaultRequest = {
uid: '1',
avatar: {
initials: 'SU',
color: 'red',
imageUrl: 'https://google.com/image1',
},
user: {
username: 'user',
email: 'some.user@google.com',
full_name: 'Some Super User',
},
};
it('has expected attributes in request', () => {
const query = UserWithProfileInfoRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it.each(['initials', 'color', 'imageUrl'])('does not returns an error if %s is null', (key) => {
const reqWithNullImage = set(defaultRequest, `avatar.${key}`, null);
const query = UserWithProfileInfoRt.decode(reqWithNullImage);
expect(query).toStrictEqual({
_tag: 'Right',
right: reqWithNullImage,
});
});
it('removes foo:bar attributes from request', () => {
const query = UserWithProfileInfoRt.decode({
...defaultRequest,
foo: 'bar',
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from avatar', () => {
const query = UserWithProfileInfoRt.decode({
...defaultRequest,
avatar: { ...defaultRequest.avatar, foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('UsersRt', () => {
const defaultRequest = [
{
email: 'reporter_no_uid@elastic.co',
full_name: 'Reporter No UID',
username: 'reporter_no_uid',
profile_uid: 'reporter-uid',
},
{
full_name: 'elastic',
email: 'testemail@elastic.co',
username: 'elastic',
},
];
it('has expected attributes in request', () => {
const query = UsersRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = UsersRt.decode([
{
...defaultRequest[0],
foo: 'bar',
},
]);
expect(query).toStrictEqual({
_tag: 'Right',
right: [defaultRequest[0]],
});
});
});
describe('GetCaseUsersResponseRt', () => {
const defaultRequest = {
assignees: [
@ -271,4 +145,89 @@ describe('User', () => {
});
});
});
describe('UserProfile', () => {
describe('SuggestUserProfilesRequestRt', () => {
const defaultRequest = {
name: 'damaged_raccoon',
owners: ['cases'],
size: 5,
};
it('has expected attributes in request', () => {
const query = SuggestUserProfilesRequestRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: {
name: 'damaged_raccoon',
owners: ['cases'],
size: 5,
},
});
});
it('has only name and owner in request', () => {
const query = SuggestUserProfilesRequestRt.decode({
name: 'damaged_raccoon',
owners: ['cases'],
foo: 'bar',
});
expect(query).toStrictEqual({
_tag: 'Right',
right: {
name: 'damaged_raccoon',
owners: ['cases'],
},
});
});
it('missing size parameter works correctly', () => {
const query = SuggestUserProfilesRequestRt.decode({
name: 'di maria',
owners: ['benfica'],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: {
name: 'di maria',
owners: ['benfica'],
},
});
});
it('removes foo:bar attributes from request', () => {
const query = SuggestUserProfilesRequestRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: {
name: 'damaged_raccoon',
owners: ['cases'],
size: 5,
},
});
});
it(`does not accept size param bigger than ${MAX_SUGGESTED_PROFILES}`, () => {
const query = SuggestUserProfilesRequestRt.decode({
...defaultRequest,
size: MAX_SUGGESTED_PROFILES + 1,
});
expect(PathReporter.report(query)).toContain('The size field cannot be more than 10.');
});
it('does not accept size param lower than 1', () => {
const query = SuggestUserProfilesRequestRt.decode({
...defaultRequest,
size: 0,
});
expect(PathReporter.report(query)).toContain('The size field cannot be less than 1.');
});
});
});
});

View file

@ -6,9 +6,22 @@
*/
import * as rt from 'io-ts';
import { MAX_SUGGESTED_PROFILES } from '../../constants';
import { limitedNumberSchema } from '../../schema';
import { MAX_SUGGESTED_PROFILES } from '../../../constants';
import { limitedNumberSchema } from '../../../schema';
import { UserWithProfileInfoRt } from '../../domain/user/v1';
export const GetCaseUsersResponseRt = rt.strict({
assignees: rt.array(UserWithProfileInfoRt),
unassignedUsers: rt.array(UserWithProfileInfoRt),
participants: rt.array(UserWithProfileInfoRt),
reporter: UserWithProfileInfoRt,
});
export type GetCaseUsersResponse = rt.TypeOf<typeof GetCaseUsersResponseRt>;
/**
* User Profiles
*/
export const SuggestUserProfilesRequestRt = rt.intersection([
rt.strict({
name: rt.string,
@ -22,9 +35,3 @@ export const SuggestUserProfilesRequestRt = rt.intersection([
]);
export type SuggestUserProfilesRequest = rt.TypeOf<typeof SuggestUserProfilesRequestRt>;
export const CaseUserProfileRt = rt.strict({
uid: rt.string,
});
export type CaseUserProfile = rt.TypeOf<typeof CaseUserProfileRt>;

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { CommentType } from '../../../api';
import { AttachmentType } from '../../domain/attachment/v1';
import { UserActionTypes } from '../../domain/user_action/action/v1';
import {
CaseUserActionStatsResponseRt,
@ -59,7 +59,7 @@ describe('User actions APIs', () => {
payload: {
comment: {
comment: 'this is a sample comment',
type: CommentType.user,
type: AttachmentType.user,
owner: 'cases',
},
},

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './v1';

View file

@ -0,0 +1,680 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
AttachmentAttributesBasicRt,
FileAttachmentMetadataRt,
SingleFileAttachmentMetadataRt,
AttachmentType,
UserCommentAttachmentPayloadRt,
AlertAttachmentPayloadRt,
ActionsAttachmentPayloadRt,
ExternalReferenceStorageType,
ExternalReferenceAttachmentPayloadRt,
PersistableStateAttachmentPayloadRt,
AttachmentRt,
UserCommentAttachmentRt,
AlertAttachmentRt,
ActionsAttachmentRt,
ExternalReferenceAttachmentRt,
PersistableStateAttachmentRt,
AttachmentPatchAttributesRt,
} from './v1';
describe('Attachments', () => {
describe('Files', () => {
describe('SingleFileAttachmentMetadataRt', () => {
const defaultRequest = {
created: '2020-02-19T23:06:33.798Z',
extension: 'png',
mimeType: 'image/png',
name: 'my-super-cool-screenshot',
};
it('has expected attributes in request', () => {
const query = SingleFileAttachmentMetadataRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = SingleFileAttachmentMetadataRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('FileAttachmentMetadataRt', () => {
const defaultRequest = {
created: '2020-02-19T23:06:33.798Z',
extension: 'png',
mimeType: 'image/png',
name: 'my-super-cool-screenshot',
};
it('has expected attributes in request', () => {
const query = FileAttachmentMetadataRt.decode({ files: [defaultRequest] });
expect(query).toStrictEqual({
_tag: 'Right',
right: {
files: [
{
...defaultRequest,
},
],
},
});
});
it('removes foo:bar attributes from request', () => {
const query = FileAttachmentMetadataRt.decode({
files: [{ ...defaultRequest, foo: 'bar' }],
});
expect(query).toStrictEqual({
_tag: 'Right',
right: {
files: [
{
...defaultRequest,
},
],
},
});
});
});
});
describe('AttachmentAttributesBasicRt', () => {
const defaultRequest = {
created_at: '2019-11-25T22:32:30.608Z',
created_by: {
full_name: 'elastic',
email: 'testemail@elastic.co',
username: 'elastic',
},
owner: 'cases',
updated_at: null,
updated_by: null,
pushed_at: null,
pushed_by: null,
};
it('has expected attributes in request', () => {
const query = AttachmentAttributesBasicRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = AttachmentAttributesBasicRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('UserCommentAttachmentPayloadRt', () => {
const defaultRequest = {
comment: 'This is a sample comment',
type: AttachmentType.user,
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = UserCommentAttachmentPayloadRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = UserCommentAttachmentPayloadRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('AlertAttachmentPayloadRt', () => {
const defaultRequest = {
alertId: 'alert-id-1',
index: 'alert-index-1',
type: AttachmentType.alert,
owner: 'cases',
rule: {
id: 'rule-id-1',
name: 'Awesome rule',
},
};
it('has expected attributes in request', () => {
const query = AlertAttachmentPayloadRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = AlertAttachmentPayloadRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from rule', () => {
const query = AlertAttachmentPayloadRt.decode({
...defaultRequest,
rule: { id: 'rule-id-1', name: 'Awesome rule', foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('ActionsAttachmentPayloadRt', () => {
const defaultRequest = {
type: AttachmentType.actions,
comment: 'I just isolated the host!',
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
},
],
type: 'isolate',
},
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = ActionsAttachmentPayloadRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = ActionsAttachmentPayloadRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from actions', () => {
const query = ActionsAttachmentPayloadRt.decode({
...defaultRequest,
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
},
],
type: 'isolate',
foo: 'bar',
},
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from targets', () => {
const query = ActionsAttachmentPayloadRt.decode({
...defaultRequest,
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
foo: 'bar',
},
],
type: 'isolate',
},
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('ExternalReferenceAttachmentPayloadRt', () => {
const defaultRequest = {
type: AttachmentType.externalReference,
externalReferenceId: 'my-id',
externalReferenceStorage: { type: ExternalReferenceStorageType.elasticSearchDoc },
externalReferenceAttachmentTypeId: '.test',
externalReferenceMetadata: { test_foo: 'foo' },
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = ExternalReferenceAttachmentPayloadRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = ExternalReferenceAttachmentPayloadRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from externalReferenceStorage', () => {
const query = ExternalReferenceAttachmentPayloadRt.decode({
...defaultRequest,
externalReferenceStorage: {
type: ExternalReferenceStorageType.elasticSearchDoc,
foo: 'bar',
},
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from externalReferenceStorage with soType', () => {
const query = ExternalReferenceAttachmentPayloadRt.decode({
...defaultRequest,
externalReferenceStorage: {
type: ExternalReferenceStorageType.savedObject,
soType: 'awesome',
foo: 'bar',
},
});
expect(query).toStrictEqual({
_tag: 'Right',
right: {
...defaultRequest,
externalReferenceStorage: {
type: ExternalReferenceStorageType.savedObject,
soType: 'awesome',
},
},
});
});
});
describe('PersistableStateAttachmentPayloadRt', () => {
const defaultRequest = {
type: AttachmentType.persistableState,
persistableStateAttachmentState: { test_foo: 'foo' },
persistableStateAttachmentTypeId: '.test',
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = PersistableStateAttachmentPayloadRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = PersistableStateAttachmentPayloadRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from persistableStateAttachmentState', () => {
const query = PersistableStateAttachmentPayloadRt.decode({
...defaultRequest,
persistableStateAttachmentState: { test_foo: 'foo', foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: {
...defaultRequest,
persistableStateAttachmentState: { test_foo: 'foo', foo: 'bar' },
},
});
});
});
describe('AttachmentRt', () => {
const defaultRequest = {
comment: 'Solve this fast!',
type: AttachmentType.user,
owner: 'cases',
id: 'basic-comment-id',
version: 'WzQ3LDFc',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
};
it('has expected attributes in request', () => {
const query = AttachmentRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = AttachmentRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('UserCommentAttachmentRt', () => {
const defaultRequest = {
comment: 'Solve this fast!',
type: AttachmentType.user,
owner: 'cases',
id: 'basic-comment-id',
version: 'WzQ3LDFc',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
};
it('has expected attributes in request', () => {
const query = UserCommentAttachmentRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = UserCommentAttachmentRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('AlertAttachmentRt', () => {
const defaultRequest = {
alertId: 'alert-id-1',
index: 'alert-index-1',
type: AttachmentType.alert,
id: 'alert-comment-id',
owner: 'cases',
rule: {
id: 'rule-id-1',
name: 'Awesome rule',
},
version: 'WzQ3LDFc',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
};
it('has expected attributes in request', () => {
const query = AlertAttachmentRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = AlertAttachmentRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from created_by', () => {
const query = AlertAttachmentRt.decode({
...defaultRequest,
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
foo: 'bar',
},
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('ActionsAttachmentRt', () => {
const defaultRequest = {
type: AttachmentType.actions,
comment: 'I just isolated the host!',
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
},
],
type: 'isolate',
},
owner: 'cases',
id: 'basic-comment-id',
version: 'WzQ3LDFc',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
};
it('has expected attributes in request', () => {
const query = ActionsAttachmentRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = ActionsAttachmentRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('ExternalReferenceAttachmentRt', () => {
const defaultRequest = {
type: AttachmentType.externalReference,
externalReferenceId: 'my-id',
externalReferenceStorage: { type: ExternalReferenceStorageType.elasticSearchDoc },
externalReferenceAttachmentTypeId: '.test',
externalReferenceMetadata: { test_foo: 'foo' },
owner: 'cases',
id: 'basic-comment-id',
version: 'WzQ3LDFc',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
};
it('has expected attributes in request', () => {
const query = ExternalReferenceAttachmentRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = ExternalReferenceAttachmentRt.decode({
...defaultRequest,
foo: 'bar',
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('PersistableStateAttachmentRt', () => {
const defaultRequest = {
type: AttachmentType.persistableState,
persistableStateAttachmentState: { test_foo: 'foo' },
persistableStateAttachmentTypeId: '.test',
owner: 'cases',
id: 'basic-comment-id',
version: 'WzQ3LDFc',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
};
it('has expected attributes in request', () => {
const query = PersistableStateAttachmentRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = PersistableStateAttachmentRt.decode({
...defaultRequest,
foo: 'bar',
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('AttachmentPatchAttributesRt', () => {
const defaultRequest = {
type: AttachmentType.actions,
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
},
],
type: 'isolate',
},
owner: 'cases',
};
it('has expected attributes in request', () => {
const query = AttachmentPatchAttributesRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = AttachmentPatchAttributesRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
});

View file

@ -0,0 +1,348 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import * as rt from 'io-ts';
import { jsonValueRt } from '../../../api';
import { UserRt } from '../user/v1';
/**
* Files
*/
export const SingleFileAttachmentMetadataRt = rt.strict({
name: rt.string,
extension: rt.string,
mimeType: rt.string,
created: rt.string,
});
export const FileAttachmentMetadataRt = rt.strict({
files: rt.array(SingleFileAttachmentMetadataRt),
});
export type FileAttachmentMetadata = rt.TypeOf<typeof FileAttachmentMetadataRt>;
export const AttachmentAttributesBasicRt = rt.strict({
created_at: rt.string,
created_by: UserRt,
owner: rt.string,
pushed_at: rt.union([rt.string, rt.null]),
pushed_by: rt.union([UserRt, rt.null]),
updated_at: rt.union([rt.string, rt.null]),
updated_by: rt.union([UserRt, rt.null]),
});
/**
* User comment
*/
export enum AttachmentType {
user = 'user',
alert = 'alert',
actions = 'actions',
externalReference = 'externalReference',
persistableState = 'persistableState',
}
export const UserCommentAttachmentPayloadRt = rt.strict({
comment: rt.string,
type: rt.literal(AttachmentType.user),
owner: rt.string,
});
const UserCommentAttachmentAttributesRt = rt.intersection([
UserCommentAttachmentPayloadRt,
AttachmentAttributesBasicRt,
]);
export const UserCommentAttachmentRt = rt.intersection([
UserCommentAttachmentAttributesRt,
rt.strict({
id: rt.string,
version: rt.string,
}),
]);
export type UserCommentAttachmentPayload = rt.TypeOf<typeof UserCommentAttachmentPayloadRt>;
export type UserCommentAttachmentAttributes = rt.TypeOf<typeof UserCommentAttachmentAttributesRt>;
export type UserCommentAttachment = rt.TypeOf<typeof UserCommentAttachmentRt>;
/**
* Alerts
*/
export const AlertAttachmentPayloadRt = rt.strict({
type: rt.literal(AttachmentType.alert),
alertId: rt.union([rt.array(rt.string), rt.string]),
index: rt.union([rt.array(rt.string), rt.string]),
rule: rt.strict({
id: rt.union([rt.string, rt.null]),
name: rt.union([rt.string, rt.null]),
}),
owner: rt.string,
});
export const AlertAttachmentAttributesRt = rt.intersection([
AlertAttachmentPayloadRt,
AttachmentAttributesBasicRt,
]);
export const AlertAttachmentRt = rt.intersection([
AlertAttachmentAttributesRt,
rt.strict({
id: rt.string,
version: rt.string,
}),
]);
export type AlertAttachmentPayload = rt.TypeOf<typeof AlertAttachmentPayloadRt>;
export type AlertAttachmentAttributes = rt.TypeOf<typeof AlertAttachmentAttributesRt>;
export type AlertAttachment = rt.TypeOf<typeof AlertAttachmentRt>;
/**
* Actions
*/
export enum IsolateHostActionType {
isolate = 'isolate',
unisolate = 'unisolate',
}
export const ActionsAttachmentPayloadRt = rt.strict({
type: rt.literal(AttachmentType.actions),
comment: rt.string,
actions: rt.strict({
targets: rt.array(
rt.strict({
hostname: rt.string,
endpointId: rt.string,
})
),
type: rt.string,
}),
owner: rt.string,
});
const ActionsAttachmentAttributesRt = rt.intersection([
ActionsAttachmentPayloadRt,
AttachmentAttributesBasicRt,
]);
export const ActionsAttachmentRt = rt.intersection([
ActionsAttachmentAttributesRt,
rt.strict({
id: rt.string,
version: rt.string,
}),
]);
export type ActionsAttachmentPayload = rt.TypeOf<typeof ActionsAttachmentPayloadRt>;
export type ActionsAttachmentAttributes = rt.TypeOf<typeof ActionsAttachmentAttributesRt>;
export type ActionsAttachment = rt.TypeOf<typeof ActionsAttachmentRt>;
/**
* External reference
*/
export enum ExternalReferenceStorageType {
savedObject = 'savedObject',
elasticSearchDoc = 'elasticSearchDoc',
}
const ExternalReferenceStorageNoSORt = rt.strict({
type: rt.literal(ExternalReferenceStorageType.elasticSearchDoc),
});
const ExternalReferenceStorageSORt = rt.strict({
type: rt.literal(ExternalReferenceStorageType.savedObject),
soType: rt.string,
});
const ExternalReferenceBaseAttachmentPayloadRt = rt.strict({
externalReferenceAttachmentTypeId: rt.string,
externalReferenceMetadata: rt.union([rt.null, rt.record(rt.string, jsonValueRt)]),
type: rt.literal(AttachmentType.externalReference),
owner: rt.string,
});
export const ExternalReferenceNoSOAttachmentPayloadRt = rt.strict({
...ExternalReferenceBaseAttachmentPayloadRt.type.props,
externalReferenceId: rt.string,
externalReferenceStorage: ExternalReferenceStorageNoSORt,
});
export const ExternalReferenceSOAttachmentPayloadRt = rt.strict({
...ExternalReferenceBaseAttachmentPayloadRt.type.props,
externalReferenceId: rt.string,
externalReferenceStorage: ExternalReferenceStorageSORt,
});
// externalReferenceId is missing.
export const ExternalReferenceSOWithoutRefsAttachmentPayloadRt = rt.strict({
...ExternalReferenceBaseAttachmentPayloadRt.type.props,
externalReferenceStorage: ExternalReferenceStorageSORt,
});
export const ExternalReferenceAttachmentPayloadRt = rt.union([
ExternalReferenceNoSOAttachmentPayloadRt,
ExternalReferenceSOAttachmentPayloadRt,
]);
export const ExternalReferenceWithoutRefsAttachmentPayloadRt = rt.union([
ExternalReferenceNoSOAttachmentPayloadRt,
ExternalReferenceSOWithoutRefsAttachmentPayloadRt,
]);
const ExternalReferenceAttachmentAttributesRt = rt.intersection([
ExternalReferenceAttachmentPayloadRt,
AttachmentAttributesBasicRt,
]);
const ExternalReferenceWithoutRefsAttachmentAttributesRt = rt.intersection([
ExternalReferenceWithoutRefsAttachmentPayloadRt,
AttachmentAttributesBasicRt,
]);
const ExternalReferenceNoSOAttachmentAttributesRt = rt.intersection([
ExternalReferenceNoSOAttachmentPayloadRt,
AttachmentAttributesBasicRt,
]);
const ExternalReferenceSOAttachmentAttributesRt = rt.intersection([
ExternalReferenceSOAttachmentPayloadRt,
AttachmentAttributesBasicRt,
]);
export const ExternalReferenceAttachmentRt = rt.intersection([
ExternalReferenceAttachmentAttributesRt,
rt.strict({
id: rt.string,
version: rt.string,
}),
]);
export type ExternalReferenceAttachmentPayload = rt.TypeOf<
typeof ExternalReferenceAttachmentPayloadRt
>;
export type ExternalReferenceSOAttachmentPayload = rt.TypeOf<
typeof ExternalReferenceSOAttachmentPayloadRt
>;
export type ExternalReferenceNoSOAttachmentPayload = rt.TypeOf<
typeof ExternalReferenceNoSOAttachmentPayloadRt
>;
export type ExternalReferenceAttachmentAttributes = rt.TypeOf<
typeof ExternalReferenceAttachmentAttributesRt
>;
export type ExternalReferenceSOAttachmentAttributes = rt.TypeOf<
typeof ExternalReferenceSOAttachmentAttributesRt
>;
export type ExternalReferenceNoSOAttachmentAttributes = rt.TypeOf<
typeof ExternalReferenceNoSOAttachmentAttributesRt
>;
export type ExternalReferenceWithoutRefsAttachmentPayload = rt.TypeOf<
typeof ExternalReferenceWithoutRefsAttachmentPayloadRt
>;
export type ExternalReferenceAttachment = rt.TypeOf<typeof ExternalReferenceAttachmentRt>;
/**
* Persistable state
*/
export const PersistableStateAttachmentPayloadRt = rt.strict({
type: rt.literal(AttachmentType.persistableState),
owner: rt.string,
persistableStateAttachmentTypeId: rt.string,
persistableStateAttachmentState: rt.record(rt.string, jsonValueRt),
});
const PersistableStateAttachmentAttributesRt = rt.intersection([
PersistableStateAttachmentPayloadRt,
AttachmentAttributesBasicRt,
]);
export const PersistableStateAttachmentRt = rt.intersection([
PersistableStateAttachmentAttributesRt,
rt.strict({
id: rt.string,
version: rt.string,
}),
]);
export type PersistableStateAttachmentPayload = rt.TypeOf<
typeof PersistableStateAttachmentPayloadRt
>;
export type PersistableStateAttachment = rt.TypeOf<typeof PersistableStateAttachmentRt>;
export type PersistableStateAttachmentAttributes = rt.TypeOf<
typeof PersistableStateAttachmentAttributesRt
>;
/**
* Common
*/
export const AttachmentAttributesRt = rt.union([
UserCommentAttachmentAttributesRt,
AlertAttachmentAttributesRt,
ActionsAttachmentAttributesRt,
ExternalReferenceAttachmentAttributesRt,
PersistableStateAttachmentAttributesRt,
]);
const AttachmentAttributesNoSORt = rt.union([
UserCommentAttachmentAttributesRt,
AlertAttachmentAttributesRt,
ActionsAttachmentAttributesRt,
ExternalReferenceNoSOAttachmentAttributesRt,
PersistableStateAttachmentAttributesRt,
]);
const AttachmentAttributesWithoutRefsRt = rt.union([
UserCommentAttachmentAttributesRt,
AlertAttachmentAttributesRt,
ActionsAttachmentAttributesRt,
ExternalReferenceWithoutRefsAttachmentAttributesRt,
PersistableStateAttachmentAttributesRt,
]);
export const AttachmentRt = rt.intersection([
AttachmentAttributesRt,
rt.strict({
id: rt.string,
version: rt.string,
}),
]);
export const AttachmentsRt = rt.array(AttachmentRt);
/**
* This type is used by the CaseService.
* Because the type for the attributes of savedObjectClient update function is Partial<T>
* we need to make all of our attributes partial too.
* We ensure that partial updates of CommentContext is not going to happen inside the patch comment route.
*/
export const AttachmentPatchAttributesRt = rt.intersection([
rt.union([
rt.exact(rt.partial(UserCommentAttachmentPayloadRt.type.props)),
rt.exact(rt.partial(AlertAttachmentPayloadRt.type.props)),
rt.exact(rt.partial(ActionsAttachmentPayloadRt.type.props)),
rt.exact(rt.partial(ExternalReferenceNoSOAttachmentPayloadRt.type.props)),
rt.exact(rt.partial(ExternalReferenceSOAttachmentPayloadRt.type.props)),
rt.exact(rt.partial(PersistableStateAttachmentPayloadRt.type.props)),
]),
rt.exact(rt.partial(AttachmentAttributesBasicRt.type.props)),
]);
export type AttachmentAttributes = rt.TypeOf<typeof AttachmentAttributesRt>;
export type AttachmentAttributesNoSO = rt.TypeOf<typeof AttachmentAttributesNoSORt>;
export type AttachmentAttributesWithoutRefs = rt.TypeOf<typeof AttachmentAttributesWithoutRefsRt>;
export type AttachmentPatchAttributes = rt.TypeOf<typeof AttachmentPatchAttributesRt>;
export type Attachment = rt.TypeOf<typeof AttachmentRt>;
export type Attachments = rt.TypeOf<typeof AttachmentsRt>;

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './v1';

View file

@ -0,0 +1,242 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { AttachmentType } from '../attachment/v1';
import { ConnectorTypes } from '../connector/v1';
import {
CaseAttributesRt,
CaseSettingsRt,
CaseSeverity,
CasesRt,
CaseStatuses,
RelatedCaseRt,
} from './v1';
const basicCase = {
owner: 'cases',
closed_at: null,
closed_by: null,
id: 'basic-case-id',
comments: [
{
comment: 'Solve this fast!',
type: AttachmentType.user,
id: 'basic-comment-id',
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
owner: 'cases',
pushed_at: null,
pushed_by: null,
updated_at: null,
updated_by: null,
version: 'WzQ3LDFc',
},
],
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
connector: {
id: 'none',
name: 'My Connector',
type: ConnectorTypes.none,
fields: null,
},
description: 'Security banana Issue',
severity: CaseSeverity.LOW,
duration: null,
external_service: null,
status: CaseStatuses.open,
tags: ['coke', 'pepsi'],
title: 'Another horrible breach!!',
totalComment: 1,
totalAlerts: 0,
updated_at: '2020-02-20T15:02:57.995Z',
updated_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
version: 'WzQ3LDFd',
settings: {
syncAlerts: true,
},
// damaged_raccoon uid
assignees: [{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }],
category: null,
};
describe('RelatedCaseRt', () => {
const defaultRequest = {
id: 'basic-case-id',
title: 'basic-case-title',
description: 'this is a simple description',
status: CaseStatuses.open,
createdAt: '2023-01-17T09:46:29.813Z',
totals: {
alerts: 5,
userComments: 2,
},
};
it('has expected attributes in request', () => {
const query = RelatedCaseRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = RelatedCaseRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from totals', () => {
const query = RelatedCaseRt.decode({
...defaultRequest,
totals: { ...defaultRequest.totals, foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('SettingsRt', () => {
it('has expected attributes in request', () => {
const query = CaseSettingsRt.decode({ syncAlerts: true });
expect(query).toStrictEqual({
_tag: 'Right',
right: { syncAlerts: true },
});
});
it('removes foo:bar attributes from request', () => {
const query = CaseSettingsRt.decode({ syncAlerts: false, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: { syncAlerts: false },
});
});
});
describe('CaseAttributesRt', () => {
const defaultRequest = {
description: 'A description',
status: CaseStatuses.open,
tags: ['new', 'case'],
title: 'My new case',
connector: {
id: '123',
name: 'My connector',
type: ConnectorTypes.jira,
fields: { issueType: 'Task', priority: 'High', parent: null },
},
settings: {
syncAlerts: true,
},
owner: 'cases',
severity: CaseSeverity.LOW,
assignees: [{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }],
duration: null,
closed_at: null,
closed_by: null,
created_at: '2020-02-19T23:06:33.798Z',
created_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
external_service: null,
updated_at: '2020-02-20T15:02:57.995Z',
updated_by: null,
category: null,
};
it('has expected attributes in request', () => {
const query = CaseAttributesRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CaseAttributesRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from connector', () => {
const query = CaseAttributesRt.decode({
...defaultRequest,
connector: { ...defaultRequest.connector, foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from created_by', () => {
const query = CaseAttributesRt.decode({
...defaultRequest,
created_by: { ...defaultRequest.created_by, foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('CasesRt', () => {
const defaultRequest = [
{
...basicCase,
},
];
it('has expected attributes in request', () => {
const query = CasesRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CasesRt.decode([{ ...defaultRequest[0], foo: 'bar' }]);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});

View file

@ -0,0 +1,147 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import * as rt from 'io-ts';
import { CaseStatuses } from '@kbn/cases-components/src/status/types';
import { ExternalServiceRt } from '../external_service/v1';
import { CaseAssigneesRt, UserRt } from '../user/v1';
import { CaseConnectorRt } from '../connector/v1';
import { AttachmentRt } from '../attachment/v1';
export { CaseStatuses };
/**
* Status
*/
export const CaseStatusRt = rt.union([
rt.literal(CaseStatuses.open),
rt.literal(CaseStatuses['in-progress']),
rt.literal(CaseStatuses.closed),
]);
export const caseStatuses = Object.values(CaseStatuses);
/**
* Severity
*/
export enum CaseSeverity {
LOW = 'low',
MEDIUM = 'medium',
HIGH = 'high',
CRITICAL = 'critical',
}
export const CaseSeverityRt = rt.union([
rt.literal(CaseSeverity.LOW),
rt.literal(CaseSeverity.MEDIUM),
rt.literal(CaseSeverity.HIGH),
rt.literal(CaseSeverity.CRITICAL),
]);
/**
* Case
*/
export const CaseSettingsRt = rt.strict({
syncAlerts: rt.boolean,
});
const CaseBasicRt = rt.strict({
/**
* The description of the case
*/
description: rt.string,
/**
* The current status of the case (open, closed, in-progress)
*/
status: CaseStatusRt,
/**
* The identifying strings for filter a case
*/
tags: rt.array(rt.string),
/**
* The title of a case
*/
title: rt.string,
/**
* The external system that the case can be synced with
*/
connector: CaseConnectorRt,
/**
* The alert sync settings
*/
settings: CaseSettingsRt,
/**
* The plugin owner of the case
*/
owner: rt.string,
/**
* The severity of the case
*/
severity: CaseSeverityRt,
/**
* The users assigned to this case
*/
assignees: CaseAssigneesRt,
/**
* The category of the case.
*/
category: rt.union([rt.string, rt.null]),
});
export const CaseAttributesRt = rt.intersection([
CaseBasicRt,
rt.strict({
duration: rt.union([rt.number, rt.null]),
closed_at: rt.union([rt.string, rt.null]),
closed_by: rt.union([UserRt, rt.null]),
created_at: rt.string,
created_by: UserRt,
external_service: rt.union([ExternalServiceRt, rt.null]),
updated_at: rt.union([rt.string, rt.null]),
updated_by: rt.union([UserRt, rt.null]),
}),
]);
export const CaseRt = rt.intersection([
CaseAttributesRt,
rt.strict({
id: rt.string,
totalComment: rt.number,
totalAlerts: rt.number,
version: rt.string,
}),
rt.exact(
rt.partial({
comments: rt.array(AttachmentRt),
})
),
]);
export const CasesRt = rt.array(CaseRt);
export const AttachmentTotalsRt = rt.strict({
alerts: rt.number,
userComments: rt.number,
});
export const RelatedCaseRt = rt.strict({
id: rt.string,
title: rt.string,
description: rt.string,
status: CaseStatusRt,
createdAt: rt.string,
totals: AttachmentTotalsRt,
});
export type Case = rt.TypeOf<typeof CaseRt>;
export type Cases = rt.TypeOf<typeof CasesRt>;
export type CaseAttributes = rt.TypeOf<typeof CaseAttributesRt>;
export type CaseSettings = rt.TypeOf<typeof CaseSettingsRt>;
export type RelatedCase = rt.TypeOf<typeof RelatedCaseRt>;
export type AttachmentTotals = rt.TypeOf<typeof AttachmentTotalsRt>;

View file

@ -6,8 +6,8 @@
*/
import * as rt from 'io-ts';
import { UserRt } from '../../../api';
import { CaseConnectorRt, ConnectorMappingsRt } from '../connector/v1';
import { UserRt } from '../user/v1';
const ClosureTypeRt = rt.union([rt.literal('close-by-user'), rt.literal('close-by-pushing')]);

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './v1';

View file

@ -0,0 +1,53 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ExternalServiceRt } from './v1';
describe('ExternalServiceRt', () => {
const defaultRequest = {
connector_id: 'servicenow-1',
connector_name: 'My SN connector',
external_id: 'external_id',
external_title: 'external title',
external_url: 'basicPush.com',
pushed_at: '2023-01-17T09:46:29.813Z',
pushed_by: {
full_name: 'Leslie Knope',
username: 'lknope',
email: 'leslie.knope@elastic.co',
},
};
it('has expected attributes in request', () => {
const query = ExternalServiceRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = ExternalServiceRt.decode({ ...defaultRequest, foo: 'bar' });
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from pushed_by', () => {
const query = ExternalServiceRt.decode({
...defaultRequest,
pushed_by: { ...defaultRequest.pushed_by, foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import * as rt from 'io-ts';
import { UserRt } from '../user/v1';
/**
* This represents the push to service UserAction. It lacks the connector_id because that is stored in a different field
* within the user action object in the API response.
*/
export const ExternalServiceBasicRt = rt.strict({
connector_name: rt.string,
external_id: rt.string,
external_title: rt.string,
external_url: rt.string,
pushed_at: rt.string,
pushed_by: UserRt,
});
export const ExternalServiceRt = rt.intersection([
rt.strict({
connector_id: rt.string,
}),
ExternalServiceBasicRt,
]);
export type ExternalService = rt.TypeOf<typeof ExternalServiceRt>;

View file

@ -8,9 +8,17 @@
// Latest
export * from './configure/latest';
export * from './user_action/latest';
export * from './external_service/latest';
export * from './case/latest';
export * from './user/latest';
export * from './connector/latest';
export * from './attachment/latest';
// V1
export * as configureDomainV1 from './configure/v1';
export * as userActionDomainV1 from './user_action/v1';
export * as externalServiceDomainV1 from './external_service/v1';
export * as caseDomainV1 from './case/v1';
export * as userDomainV1 from './user/v1';
export * as connectorDomainV1 from './connector/v1';
export * as attachmentDomainV1 from './attachment/v1';

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './v1';

View file

@ -0,0 +1,202 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { set } from 'lodash';
import { UserRt, UserWithProfileInfoRt, UsersRt, CaseUserProfileRt, CaseAssigneesRt } from './v1';
describe('User', () => {
describe('UserRt', () => {
const defaultRequest = {
full_name: 'elastic',
email: 'testemail@elastic.co',
username: 'elastic',
profile_uid: 'profile-uid-1',
};
it('has expected attributes in request', () => {
const query = UserRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = UserRt.decode({
...defaultRequest,
foo: 'bar',
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('UserWithProfileInfoRt', () => {
const defaultRequest = {
uid: '1',
avatar: {
initials: 'SU',
color: 'red',
imageUrl: 'https://google.com/image1',
},
user: {
username: 'user',
email: 'some.user@google.com',
full_name: 'Some Super User',
},
};
it('has expected attributes in request', () => {
const query = UserWithProfileInfoRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it.each(['initials', 'color', 'imageUrl'])('does not returns an error if %s is null', (key) => {
const reqWithNullImage = set(defaultRequest, `avatar.${key}`, null);
const query = UserWithProfileInfoRt.decode(reqWithNullImage);
expect(query).toStrictEqual({
_tag: 'Right',
right: reqWithNullImage,
});
});
it('removes foo:bar attributes from request', () => {
const query = UserWithProfileInfoRt.decode({
...defaultRequest,
foo: 'bar',
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from avatar', () => {
const query = UserWithProfileInfoRt.decode({
...defaultRequest,
avatar: { ...defaultRequest.avatar, foo: 'bar' },
});
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
});
describe('UsersRt', () => {
const defaultRequest = [
{
email: 'reporter_no_uid@elastic.co',
full_name: 'Reporter No UID',
username: 'reporter_no_uid',
profile_uid: 'reporter-uid',
},
{
full_name: 'elastic',
email: 'testemail@elastic.co',
username: 'elastic',
},
];
it('has expected attributes in request', () => {
const query = UsersRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = UsersRt.decode([
{
...defaultRequest[0],
foo: 'bar',
},
]);
expect(query).toStrictEqual({
_tag: 'Right',
right: [defaultRequest[0]],
});
});
});
describe('UserProfile', () => {
describe('CaseUserProfileRt', () => {
it('has expected attributes in response', () => {
const query = CaseUserProfileRt.decode({
uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
});
expect(query).toStrictEqual({
_tag: 'Right',
right: {
uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
},
});
});
it('removes foo:bar attributes from response', () => {
const query = CaseUserProfileRt.decode({
uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
foo: 'bar',
});
expect(query).toStrictEqual({
_tag: 'Right',
right: {
uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0',
},
});
});
});
});
describe('Assignee', () => {
describe('CaseAssigneesRt', () => {
const defaultRequest = [{ uid: '1' }, { uid: '2' }];
it('has expected attributes in request', () => {
const query = CaseAssigneesRt.decode(defaultRequest);
expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});
it('removes foo:bar attributes from request', () => {
const query = CaseAssigneesRt.decode([{ ...defaultRequest[0], foo: 'bar' }]);
expect(query).toStrictEqual({
_tag: 'Right',
right: [defaultRequest[0]],
});
});
it('removes foo:bar attributes from assignees', () => {
const query = CaseAssigneesRt.decode([{ uid: '1', foo: 'bar' }, { uid: '2' }]);
expect(query).toStrictEqual({
_tag: 'Right',
right: [{ uid: '1' }, { uid: '2' }],
});
});
});
});
});

View file

@ -41,11 +41,14 @@ export const UsersRt = rt.array(UserRt);
export type User = rt.TypeOf<typeof UserRt>;
export type UserWithProfileInfo = rt.TypeOf<typeof UserWithProfileInfoRt>;
export const GetCaseUsersResponseRt = rt.strict({
assignees: rt.array(UserWithProfileInfoRt),
unassignedUsers: rt.array(UserWithProfileInfoRt),
participants: rt.array(UserWithProfileInfoRt),
reporter: UserWithProfileInfoRt,
export const CaseUserProfileRt = rt.strict({
uid: rt.string,
});
export type GetCaseUsersResponse = rt.TypeOf<typeof GetCaseUsersResponseRt>;
export type CaseUserProfile = rt.TypeOf<typeof CaseUserProfileRt>;
/**
* Assignees
*/
export const CaseAssigneesRt = rt.array(CaseUserProfileRt);
export type CaseAssignees = rt.TypeOf<typeof CaseAssigneesRt>;

View file

@ -6,7 +6,7 @@
*/
import * as rt from 'io-ts';
import { CaseAssigneesRt } from '../../../../api';
import { CaseAssigneesRt } from '../../user/v1';
import { UserActionTypes } from '../action/v1';
export const AssigneesUserActionPayloadRt = rt.strict({ assignees: CaseAssigneesRt });

View file

@ -5,16 +5,16 @@
* 2.0.
*/
import { CommentType } from '../../../../api';
import { AttachmentType } from '../../attachment/v1';
import { UserActionTypes } from '../action/v1';
import { CommentUserActionPayloadRt, CommentUserActionRt } from './v1';
describe('Comment', () => {
describe('Attachment', () => {
describe('CommentUserActionPayloadRt', () => {
const defaultRequest = {
comment: {
comment: 'this is a sample comment',
type: CommentType.user,
type: AttachmentType.user,
owner: 'cases',
},
};
@ -54,7 +54,7 @@ describe('Comment', () => {
payload: {
comment: {
comment: 'this is a sample comment',
type: CommentType.user,
type: AttachmentType.user,
owner: 'cases',
},
},

View file

@ -6,12 +6,12 @@
*/
import * as rt from 'io-ts';
import { CommentRequestRt, CommentRequestWithoutRefsRt } from '../../../../api';
import { AttachmentRequestRt, AttachmentRequestWithoutRefsRt } from '../../../api/attachment/v1';
import { UserActionTypes } from '../action/v1';
export const CommentUserActionPayloadRt = rt.strict({ comment: CommentRequestRt });
export const CommentUserActionPayloadRt = rt.strict({ comment: AttachmentRequestRt });
export const CommentUserActionPayloadWithoutIdsRt = rt.strict({
comment: CommentRequestWithoutRefsRt,
comment: AttachmentRequestWithoutRefsRt,
});
export const CommentUserActionRt = rt.strict({

View file

@ -6,15 +6,15 @@
*/
import * as rt from 'io-ts';
import { CaseExternalServiceBasicRt, CaseUserActionExternalServiceRt } from '../../../../api';
import { ExternalServiceBasicRt, ExternalServiceRt } from '../../external_service/v1';
import { UserActionTypes } from '../action/v1';
export const PushedUserActionPayloadWithoutConnectorIdRt = rt.strict({
externalService: CaseUserActionExternalServiceRt,
externalService: ExternalServiceBasicRt,
});
export const PushedUserActionPayloadRt = rt.strict({
externalService: CaseExternalServiceBasicRt,
externalService: ExternalServiceRt,
});
export const PushedUserActionWithoutConnectorIdRt = rt.strict({

View file

@ -6,10 +6,10 @@
*/
import * as rt from 'io-ts';
import { SettingsRt } from '../../../../api';
import { CaseSettingsRt } from '../../case/v1';
import { UserActionTypes } from '../action/v1';
export const SettingsUserActionPayloadRt = rt.strict({ settings: SettingsRt });
export const SettingsUserActionPayloadRt = rt.strict({ settings: CaseSettingsRt });
export const SettingsUserActionRt = rt.strict({
type: rt.literal(UserActionTypes.settings),

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { CaseSeverity } from '../../../../api';
import { CaseSeverity } from '../../case/v1';
import { UserActionTypes } from '../action/v1';
import { SeverityUserActionPayloadRt, SeverityUserActionRt } from './v1';

View file

@ -6,7 +6,7 @@
*/
import * as rt from 'io-ts';
import { CaseSeverityRt } from '../../../../api';
import { CaseSeverityRt } from '../../case/v1';
import { UserActionTypes } from '../action/v1';
export const SeverityUserActionPayloadRt = rt.strict({ severity: CaseSeverityRt });

View file

@ -6,7 +6,7 @@
*/
import * as rt from 'io-ts';
import { CaseStatusRt } from '../../../../api';
import { CaseStatusRt } from '../../case/v1';
import { UserActionTypes } from '../action/v1';
export const StatusUserActionPayloadRt = rt.strict({ status: CaseStatusRt });

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { CommentType } from '../../../api';
import { AttachmentType } from '../attachment/v1';
import { UserActionTypes } from './action/v1';
import { UserActionsRt } from './v1';
@ -17,7 +17,7 @@ describe('User actions', () => {
payload: {
comment: {
comment: 'this is a sample comment',
type: CommentType.user,
type: AttachmentType.user,
owner: 'cases',
},
},

View file

@ -6,7 +6,7 @@
*/
import * as rt from 'io-ts';
import { UserRt } from '../../../api';
import { UserRt } from '../user/v1';
import { UserActionActionsRt } from './action/v1';
import { AssigneesUserActionRt } from './assignees/v1';
import { CategoryUserActionRt } from './category/v1';

View file

@ -12,28 +12,28 @@ import type {
READ_CASES_CAPABILITY,
UPDATE_CASES_CAPABILITY,
} from '..';
import type {
CasePatchRequest,
CaseStatuses,
User,
SingleCaseMetricsResponse,
Comment,
Case as CaseSnakeCase,
CommentResponseAlertsType,
CasesFindResponse,
CasesStatusResponse,
CasesMetricsResponse,
CaseSeverity,
CommentResponseExternalReferenceType,
CommentResponseTypePersistableState,
GetCaseUsersResponse,
} from '../api';
import type { SingleCaseMetricsResponse, CasesMetricsResponse } from '../api';
import type { PUSH_CASES_CAPABILITY } from '../constants';
import type { SnakeToCamelCase } from '../types';
import type { ActionConnector, UserAction } from '../types/domain';
import type {
CaseSeverity,
CaseStatuses,
UserAction,
Case as CaseSnakeCase,
User,
ActionConnector,
AlertAttachment,
Attachment,
ExternalReferenceAttachment,
PersistableStateAttachment,
} from '../types/domain';
import type {
CasePatchRequest,
CasesFindResponse,
CasesStatusResponse,
CaseUserActionStatsResponse,
GetCaseConnectorsResponse,
GetCaseUsersResponse,
UserActionFindRequestTypes,
UserActionFindResponse,
} from '../types/api';
@ -84,16 +84,18 @@ export type CaseViewRefreshPropInterface = null | {
refreshCase: () => Promise<void>;
};
export type CommentUI = SnakeToCamelCase<Comment>;
export type AlertComment = SnakeToCamelCase<CommentResponseAlertsType>;
export type ExternalReferenceComment = SnakeToCamelCase<CommentResponseExternalReferenceType>;
export type PersistableComment = SnakeToCamelCase<CommentResponseTypePersistableState>;
export type AttachmentUI = SnakeToCamelCase<Attachment>;
export type AlertAttachmentUI = SnakeToCamelCase<AlertAttachment>;
export type ExternalReferenceAttachmentUI = SnakeToCamelCase<ExternalReferenceAttachment>;
export type PersistableStateAttachmentUI = SnakeToCamelCase<PersistableStateAttachment>;
export type UserActionUI = SnakeToCamelCase<UserAction>;
export type FindCaseUserActions = Omit<SnakeToCamelCase<UserActionFindResponse>, 'userActions'> & {
userActions: UserActionUI[];
};
export type CaseUserActionsStats = SnakeToCamelCase<CaseUserActionStatsResponse>;
export type CaseUI = Omit<SnakeToCamelCase<CaseSnakeCase>, 'comments'> & { comments: CommentUI[] };
export type CaseUI = Omit<SnakeToCamelCase<CaseSnakeCase>, 'comments'> & {
comments: AttachmentUI[];
};
export type CasesUI = CaseUI[];
export type CasesFindResponseUI = Omit<SnakeToCamelCase<CasesFindResponse>, 'cases'> & {
cases: CasesUI;

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import type { CommentAttributes } from '../api';
import { CommentType } from '../api';
import type { AttachmentAttributes } from '../types/domain';
import { AttachmentType } from '../types/domain';
import {
isCommentRequestTypeExternalReference,
isCommentRequestTypePersistableState,
@ -15,11 +15,11 @@ import {
describe('attachments utils', () => {
describe('isCommentRequestTypeExternalReference', () => {
const externalReferenceAttachment = {
type: CommentType.externalReference as const,
} as CommentAttributes;
type: AttachmentType.externalReference as const,
} as AttachmentAttributes;
const commentTypeWithoutAlert = Object.values(CommentType).filter(
(type) => type !== CommentType.externalReference
const commentTypeWithoutAlert = Object.values(AttachmentType).filter(
(type) => type !== AttachmentType.externalReference
);
it('returns false for type: externalReference', () => {
@ -29,7 +29,7 @@ describe('attachments utils', () => {
it.each(commentTypeWithoutAlert)('returns false for type: %s', (type) => {
const attachment = {
type,
} as CommentAttributes;
} as AttachmentAttributes;
expect(isCommentRequestTypeExternalReference(attachment)).toBe(false);
});
@ -37,11 +37,11 @@ describe('attachments utils', () => {
describe('isCommentRequestTypePersistableState', () => {
const persistableStateAttachment = {
type: CommentType.persistableState as const,
} as CommentAttributes;
type: AttachmentType.persistableState as const,
} as AttachmentAttributes;
const commentTypeWithoutAlert = Object.values(CommentType).filter(
(type) => type !== CommentType.persistableState
const commentTypeWithoutAlert = Object.values(AttachmentType).filter(
(type) => type !== AttachmentType.persistableState
);
it('returns false for type: persistableState', () => {
@ -51,7 +51,7 @@ describe('attachments utils', () => {
it.each(commentTypeWithoutAlert)('returns false for type: %s', (type) => {
const attachment = {
type,
} as CommentAttributes;
} as AttachmentAttributes;
expect(isCommentRequestTypePersistableState(attachment)).toBe(false);
});

View file

@ -5,27 +5,27 @@
* 2.0.
*/
import type { AttachmentRequest } from '../types/api';
import type {
CommentRequest,
CommentRequestExternalReferenceType,
CommentRequestPersistableStateType,
} from '../api';
import { CommentType } from '../api';
ExternalReferenceAttachmentPayload,
PersistableStateAttachmentPayload,
} from '../types/domain';
import { AttachmentType } from '../types/domain';
/**
* A type narrowing function for external reference attachments.
*/
export const isCommentRequestTypeExternalReference = (
context: CommentRequest
): context is CommentRequestExternalReferenceType => {
return context.type === CommentType.externalReference;
context: AttachmentRequest
): context is ExternalReferenceAttachmentPayload => {
return context.type === AttachmentType.externalReference;
};
/**
* A type narrowing function for persistable state attachments.
*/
export const isCommentRequestTypePersistableState = (
context: Partial<CommentRequest>
): context is CommentRequestPersistableStateType => {
return context.type === CommentType.persistableState;
context: Partial<AttachmentRequest>
): context is PersistableStateAttachmentPayload => {
return context.type === AttachmentType.persistableState;
};

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import type { CaseAssignees } from '../api';
import { MAX_ASSIGNEES_PER_CASE } from '../constants';
import type { CaseAssignees } from '../types/domain';
export const areTotalAssigneesInvalid = (assignees?: CaseAssignees): boolean => {
if (assignees == null) {

View file

@ -5,10 +5,11 @@
* 2.0.
*/
import type { CasesFindRequest, CasesMetricsRequest } from '../../../common/api';
import type { CasesMetricsRequest } from '../../../common/api';
import type { HTTPService } from '..';
import { casesMetrics, casesStatus } from '../../containers/mock';
import type { CasesMetrics, CasesStatus } from '../../containers/types';
import type { CasesFindRequest } from '../../../common/types/api';
export const getCasesStatus = async ({
http,

View file

@ -9,20 +9,20 @@ import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import { createToasterPlainError } from '../containers/utils';
import { throwErrors } from '../../common';
import type {
CasesFindResponse,
CasesStatusResponse,
CasesMetricsResponse,
CasesBulkGetResponse,
} from '../../common/api';
} from '../../common/types/api';
import {
CasesBulkGetResponseRt,
CasesFindResponseRt,
CasesStatusResponseRt,
CasesMetricsResponseRt,
} from '../../common/api';
CasesBulkGetResponseRt,
} from '../../common/types/api';
import { createToasterPlainError } from '../containers/utils';
import { throwErrors } from '../../common';
import type { CasesMetricsResponse } from '../../common/api';
import { CasesMetricsResponseRt } from '../../common/api';
export const decodeCasesFindResponse = (respCases?: CasesFindResponse) =>
pipe(CasesFindResponseRt.decode(respCases), fold(throwErrors(createToasterPlainError), identity));

View file

@ -6,6 +6,14 @@
*/
import type { HttpStart } from '@kbn/core/public';
import type {
CasesFindRequest,
CasesFindResponse,
CasesStatusRequest,
CasesStatusResponse,
CasesBulkGetRequest,
CasesBulkGetResponse,
} from '../../common/types/api';
import type { CasesStatus, CasesMetrics, CasesFindResponseUI } from '../../common/ui';
import {
CASE_FIND_URL,
@ -13,16 +21,7 @@ import {
CASE_STATUS_URL,
INTERNAL_BULK_GET_CASES_URL,
} from '../../common/constants';
import type {
CasesBulkGetRequest,
CasesBulkGetResponse,
CasesFindRequest,
CasesFindResponse,
CasesMetricsRequest,
CasesMetricsResponse,
CasesStatusRequest,
CasesStatusResponse,
} from '../../common/api';
import type { CasesMetricsRequest, CasesMetricsResponse } from '../../common/api';
import { convertAllCasesToCamel, convertToCamelCase } from './utils';
import {
decodeCasesBulkGetResponse,

View file

@ -7,25 +7,22 @@
import { set } from '@kbn/safer-lodash-set';
import { isArray, camelCase, isObject, omit, get } from 'lodash';
import type { UserActions } from '../../common/types/domain';
import type {
AttachmentRequest,
CaseResolveResponse,
CasesFindResponse,
} from '../../common/types/api';
import type { Attachment, Case, Cases, UserActions } from '../../common/types/domain';
import {
isCommentRequestTypeExternalReference,
isCommentRequestTypePersistableState,
} from '../../common/utils/attachments';
import type {
CasesFindResponse,
Case,
CommentRequest,
Comment,
CaseResolveResponse,
Cases,
} from '../../common/api';
import { isCommentUserAction } from '../../common/utils/user_actions';
import type {
CasesFindResponseUI,
CasesUI,
CaseUI,
CommentUI,
AttachmentUI,
ResolvedCase,
} from '../containers/types';
@ -71,11 +68,11 @@ export const convertCaseResolveToCamelCase = (res: CaseResolveResponse): Resolve
};
};
export const convertAttachmentsToCamelCase = (attachments: Comment[]): CommentUI[] => {
export const convertAttachmentsToCamelCase = (attachments: Attachment[]): AttachmentUI[] => {
return attachments.map((attachment) => convertAttachmentToCamelCase(attachment));
};
export const convertAttachmentToCamelCase = (attachment: CommentRequest): CommentUI => {
export const convertAttachmentToCamelCase = (attachment: AttachmentRequest): AttachmentUI => {
if (isCommentRequestTypeExternalReference(attachment)) {
return convertAttachmentToCamelExceptProperty(attachment, 'externalReferenceMetadata');
}
@ -84,7 +81,7 @@ export const convertAttachmentToCamelCase = (attachment: CommentRequest): Commen
return convertAttachmentToCamelExceptProperty(attachment, 'persistableStateAttachmentState');
}
return convertToCamelCase<CommentRequest, CommentUI>(attachment);
return convertToCamelCase<AttachmentRequest, AttachmentUI>(attachment);
};
export const convertUserActionsToCamelCase = (userActions: UserActions) => {
@ -106,9 +103,9 @@ export const convertUserActionsToCamelCase = (userActions: UserActions) => {
};
const convertAttachmentToCamelExceptProperty = (
attachment: CommentRequest,
attachment: AttachmentRequest,
key: string
): CommentUI => {
): AttachmentUI => {
const intactValue = get(attachment, key);
const attachmentWithoutIntactValue = omit(attachment, key);
const camelCaseAttachmentWithoutIntactValue = convertToCamelCase(attachmentWithoutIntactValue);
@ -116,7 +113,7 @@ const convertAttachmentToCamelExceptProperty = (
return {
...camelCaseAttachmentWithoutIntactValue,
[key]: intactValue,
} as CommentUI;
} as AttachmentUI;
};
export const convertAllCasesToCamel = (snakeCases: CasesFindResponse): CasesFindResponseUI => ({

View file

@ -7,12 +7,12 @@
import type { HttpStart } from '@kbn/core/public';
import type {
CasesByAlertId,
CasesByAlertIDRequest,
GetRelatedCasesByAlertResponse,
CasesFindRequest,
CasesStatusRequest,
CasesMetricsRequest,
} from '../../../common/api';
} from '../../../common/types/api';
import type { CasesMetricsRequest } from '../../../common/api';
import { getCasesFromAlertsUrl } from '../../../common/api';
import { bulkGetCases, getCases, getCasesMetrics, getCasesStatus } from '../../api';
import type { CasesFindResponseUI, CasesStatus, CasesMetrics } from '../../../common/ui';
@ -23,8 +23,8 @@ export const createClientAPI = ({ http }: { http: HttpStart }): CasesUiStart['ap
getRelatedCases: async (
alertId: string,
query: CasesByAlertIDRequest
): Promise<CasesByAlertId> =>
http.get<CasesByAlertId>(getCasesFromAlertsUrl(alertId), { query }),
): Promise<GetRelatedCasesByAlertResponse> =>
http.get<GetRelatedCasesByAlertResponse>(getCasesFromAlertsUrl(alertId), { query }),
cases: {
find: (query: CasesFindRequest, signal?: AbortSignal): Promise<CasesFindResponseUI> =>
getCases({ http, query, signal }),

View file

@ -8,9 +8,9 @@
import type React from 'react';
import type { EuiCommentProps, IconType, EuiButtonProps } from '@elastic/eui';
import type {
CommentRequestExternalReferenceType,
CommentRequestPersistableStateType,
} from '../../../common/api';
ExternalReferenceAttachmentPayload,
PersistableStateAttachmentPayload,
} from '../../../common/types/domain';
import type { CaseUI } from '../../containers/types';
export enum AttachmentActionType {
@ -53,13 +53,13 @@ export interface CommonAttachmentViewProps {
}
export interface ExternalReferenceAttachmentViewProps extends CommonAttachmentViewProps {
externalReferenceId: CommentRequestExternalReferenceType['externalReferenceId'];
externalReferenceMetadata: CommentRequestExternalReferenceType['externalReferenceMetadata'];
externalReferenceId: ExternalReferenceAttachmentPayload['externalReferenceId'];
externalReferenceMetadata: ExternalReferenceAttachmentPayload['externalReferenceMetadata'];
}
export interface PersistableStateAttachmentViewProps extends CommonAttachmentViewProps {
persistableStateAttachmentTypeId: CommentRequestPersistableStateType['persistableStateAttachmentTypeId'];
persistableStateAttachmentState: CommentRequestPersistableStateType['persistableStateAttachmentState'];
persistableStateAttachmentTypeId: PersistableStateAttachmentPayload['persistableStateAttachmentTypeId'];
persistableStateAttachmentState: PersistableStateAttachmentPayload['persistableStateAttachmentState'];
}
export interface AttachmentType<Props> {

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import type { CommentRequestAlertType } from '../../../common/api';
import type { AlertAttachmentPayload } from '../../../common/types/domain';
import type { Ecs } from '../../../common';
import { CommentType } from '../../../common';
import { AttachmentType } from '../../../common/types/domain';
import { getRuleIdFromEvent } from './get_rule_id_from_event';
import type { CaseAttachmentsWithoutOwner } from '../../types';
@ -21,7 +21,7 @@ interface EventNonEcsData {
value?: Maybe<string[]>;
}
type CommentRequestAlertTypeWithoutOwner = Omit<CommentRequestAlertType, 'owner'>;
type CommentRequestAlertTypeWithoutOwner = Omit<AlertAttachmentPayload, 'owner'>;
export type GroupAlertsByRule = (items: Event[]) => CaseAttachmentsWithoutOwner;
@ -33,7 +33,7 @@ export const groupAlertsByRule: GroupAlertsByRule = (items) => {
acc[rule.id] = {
alertId: [],
index: [],
type: CommentType.alert as const,
type: AttachmentType.alert as const,
rule,
};
}

View file

@ -7,7 +7,7 @@
import type { Transaction } from '@elastic/apm-rum';
import { useCallback } from 'react';
import { CommentType } from '../../../common';
import { AttachmentType } from '../../../common/types/domain';
import type { CaseAttachmentsWithoutOwner } from '../../types';
import { useStartTransaction } from './use_start_transaction';
@ -77,7 +77,7 @@ export const useAddAttachmentToExistingCaseTransaction = () => {
const getAlertCount = (attachments: CaseAttachmentsWithoutOwner) => {
return attachments.reduce((total, attachment) => {
if (attachment.type !== CommentType.alert) {
if (attachment.type !== AttachmentType.alert) {
return total;
}
if (!Array.isArray(attachment.alertId)) {

View file

@ -12,7 +12,7 @@ import styled from 'styled-components';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { isValidOwner } from '../../common/utils/owner';
import type { CaseUI } from '../../common';
import { CommentType } from '../../common';
import { AttachmentType } from '../../common/types/domain';
import { useKibana, useToasts } from './lib/kibana';
import { generateCaseViewPath } from './navigation';
import type { CaseAttachmentsWithoutOwner, ServerError } from '../types';
@ -43,7 +43,7 @@ const EuiTextStyled = styled(EuiText)`
function getAlertsCount(attachments: CaseAttachmentsWithoutOwner): number {
let alertsCount = 0;
for (const attachment of attachments) {
if (attachment.type === CommentType.alert) {
if (attachment.type === AttachmentType.alert) {
// alertId might be an array
if (Array.isArray(attachment.alertId) && attachment.alertId.length > 1) {
alertsCount += attachment.alertId.length;
@ -91,7 +91,7 @@ function getToastContent({
}
if (attachments !== undefined) {
for (const attachment of attachments) {
if (attachment.type === CommentType.alert && theCase.settings.syncAlerts) {
if (attachment.type === AttachmentType.alert && theCase.settings.syncAlerts) {
return CASE_ALERT_SUCCESS_SYNC_TEXT;
}
}

View file

@ -6,7 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import { CaseSeverity } from '../../../../common/api';
import { CaseSeverity } from '../../../../common/types/domain';
import { severities } from '../../severity/config';
const SET_SEVERITY = ({

View file

@ -12,7 +12,7 @@ import { useSeverityAction } from './use_severity_action';
import * as api from '../../../containers/api';
import { basicCase } from '../../../containers/mock';
import { CaseSeverity } from '../../../../common/api';
import { CaseSeverity } from '../../../../common/types/domain';
jest.mock('../../../containers/api');

View file

@ -7,7 +7,7 @@
import { useCallback } from 'react';
import type { EuiContextMenuPanelItemDescriptor } from '@elastic/eui';
import { CaseSeverity } from '../../../../common/api';
import { CaseSeverity } from '../../../../common/types/domain';
import { useUpdateCases } from '../../../containers/use_bulk_update_case';
import type { CasesUI } from '../../../../common';

View file

@ -12,7 +12,7 @@ import { useStatusAction } from './use_status_action';
import * as api from '../../../containers/api';
import { basicCase } from '../../../containers/mock';
import { CaseStatuses } from '../../../../common';
import { CaseStatuses } from '../../../../common/types/domain';
jest.mock('../../../containers/api');

View file

@ -9,7 +9,7 @@ import { useCallback } from 'react';
import type { EuiContextMenuPanelItemDescriptor } from '@elastic/eui';
import { useUpdateCases } from '../../../containers/use_bulk_update_case';
import type { CasesUI } from '../../../../common';
import { CaseStatuses } from '../../../../common';
import { CaseStatuses } from '../../../../common/types/domain';
import * as i18n from './translations';
import type { UseActionProps } from '../types';

View file

@ -12,7 +12,7 @@ import { noop } from 'lodash/fp';
import { noCreateCasesPermissions, TestProviders, createAppMockRenderer } from '../../common/mock';
import { CommentType } from '../../../common/api';
import { AttachmentType } from '../../../common/types/domain';
import { SECURITY_SOLUTION_OWNER, MAX_COMMENT_LENGTH } from '../../../common/constants';
import { useCreateAttachments } from '../../containers/use_create_attachments';
import type { AddCommentProps, AddCommentRefObject } from '.';
@ -46,7 +46,7 @@ const defaultResponse = {
const sampleData: CaseAttachmentWithoutOwner = {
comment: 'what a cool comment',
type: CommentType.user as const,
type: AttachmentType.user as const,
};
const appId = 'testAppId';
const draftKey = `cases.${appId}.${addCommentProps.caseId}.${addCommentProps.id}.markdownEditor`;

View file

@ -23,7 +23,7 @@ import {
UseField,
useFormData,
} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import { CommentType } from '../../../common/api';
import { AttachmentType } from '../../../common/types/domain';
import { useCreateAttachments } from '../../containers/use_create_attachments';
import type { CaseUI } from '../../containers/types';
import type { EuiMarkdownEditorRef } from '../markdown_editor';
@ -119,7 +119,7 @@ export const AddComment = React.memo(
{
caseId,
caseOwner: owner[0],
attachments: [{ ...data, type: CommentType.user }],
attachments: [{ ...data, type: AttachmentType.user }],
},
{
onSuccess: (theCase) => {

View file

@ -8,7 +8,7 @@
import type { FormSchema } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import { FIELD_TYPES } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
import type { CommentRequestUserType } from '../../../common/api';
import type { UserCommentAttachmentPayload } from '../../../common/types/domain';
import { MAX_COMMENT_LENGTH } from '../../../common/constants';
import * as i18n from './translations';
@ -16,7 +16,7 @@ import * as i18n from './translations';
const { emptyField, maxLengthField } = fieldValidators;
export interface AddCommentFormSchema {
comment: CommentRequestUserType['comment'];
comment: UserCommentAttachmentPayload['comment'];
}
export const schema: FormSchema<AddCommentFormSchema> = {

View file

@ -23,7 +23,7 @@ import {
import { useGetCasesMockState, connectorsMock } from '../../containers/mock';
import { SortFieldCase, StatusAll } from '../../../common/ui/types';
import { CaseSeverity, CaseStatuses } from '../../../common/api';
import { CaseSeverity, CaseStatuses } from '../../../common/types/domain';
import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { getEmptyTagValue } from '../empty_value';
import { useKibana } from '../../common/lib/kibana';

View file

@ -21,7 +21,7 @@ import type { CasesOwners } from '../../client/helpers/can_use_cases';
import type { EuiBasicTableOnChange, Solution } from './types';
import { SortFieldCase, StatusAll } from '../../../common/ui/types';
import { CaseStatuses, caseStatuses } from '../../../common/api';
import { CaseStatuses, caseStatuses } from '../../../common/types/domain';
import { OWNER_INFO } from '../../../common/constants';
import { useAvailableCasesOwners } from '../app/use_available_owners';
import { useCasesColumns } from './use_cases_columns';

View file

@ -16,7 +16,7 @@ import {
} from '@elastic/eui';
import styled, { css } from 'styled-components';
import prettyMilliseconds from 'pretty-ms';
import { CaseStatuses } from '../../../common/api';
import { CaseStatuses } from '../../../common/types/domain';
import { useGetCasesStatus } from '../../containers/use_get_cases_status';
import { StatusStats } from '../status/status_stats';
import { useGetCasesMetrics } from '../../containers/use_get_cases_metrics';

View file

@ -11,7 +11,8 @@ import userEvent from '@testing-library/user-event';
import React from 'react';
import AllCasesSelectorModal from '.';
import type { CaseUI } from '../../../../common';
import { CaseStatuses, StatusAll } from '../../../../common';
import { StatusAll } from '../../../../common';
import { CaseStatuses } from '../../../../common/types/domain';
import type { AppMockRenderer } from '../../../common/mock';
import { allCasesPermissions, createAppMockRenderer } from '../../../common/mock';
import { useCasesToast } from '../../../common/use_cases_toast';

View file

@ -6,10 +6,11 @@
*/
import { useCallback } from 'react';
import { CaseStatuses, StatusAll } from '../../../../common';
import { CaseStatuses } from '../../../../common/types/domain';
import type { AllCasesSelectorModalProps } from '.';
import { useCasesToast } from '../../../common/use_cases_toast';
import type { CaseUI } from '../../../containers/types';
import { StatusAll } from '../../../containers/types';
import { CasesContextStoreActionsList } from '../../cases_context/cases_context_reducer';
import { useCasesContext } from '../../cases_context/use_cases_context';
import { useCasesAddToNewCaseFlyout } from '../../create/flyout/use_cases_add_to_new_case_flyout';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { CaseSeverity } from '../../../common/api';
import { CaseSeverity } from '../../../common/types/domain';
import React from 'react';
import type { AppMockRenderer } from '../../common/mock';
import { createAppMockRenderer } from '../../common/mock';

View file

@ -10,7 +10,7 @@ import { mount } from 'enzyme';
import { waitFor } from '@testing-library/react';
import { StatusAll } from '../../../common/ui/types';
import { CaseStatuses } from '../../../common/api';
import { CaseStatuses } from '../../../common/types/domain';
import { StatusFilter } from './status_filter';
const stats = {

View file

@ -12,7 +12,7 @@ import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl';
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
import { waitForComponentToUpdate } from '../../common/test_utils';
import { CaseStatuses } from '../../../common/api';
import { CaseStatuses } from '../../../common/types/domain';
import {
OWNER_INFO,
SECURITY_SOLUTION_OWNER,

View file

@ -13,7 +13,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiFilterGroup, EuiButton }
import type { CaseStatusWithAllStatus, CaseSeverityWithAll } from '../../../common/ui/types';
import { MAX_TAGS_FILTER_LENGTH, MAX_CATEGORY_FILTER_LENGTH } from '../../../common/constants';
import { StatusAll } from '../../../common/ui/types';
import { CaseStatuses } from '../../../common/api';
import { CaseStatuses } from '../../../common/types/domain';
import type { FilterOptions } from '../../containers/types';
import { FilterPopover } from '../filter_popover';
import { SolutionFilter } from './solution_filter';

View file

@ -17,7 +17,7 @@ import {
} from './use_all_cases_state';
import { DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from '../../containers/use_get_cases';
import { DEFAULT_TABLE_ACTIVE_PAGE, DEFAULT_TABLE_LIMIT } from '../../containers/constants';
import { CaseStatuses } from '../../../common';
import { CaseStatuses } from '../../../common/types/domain';
import { SortFieldCase } from '../../containers/types';
import { stringifyToURL } from '../utils';

View file

@ -17,7 +17,7 @@ import { connectors } from '../configure_cases/__mock__';
import type { AppMockRenderer } from '../../common/mock';
import { createAppMockRenderer, readCasesPermissions, TestProviders } from '../../common/mock';
import { renderHook } from '@testing-library/react-hooks';
import { CaseStatuses } from '../../../common';
import { CaseStatuses } from '../../../common/types/domain';
import { userProfilesMap } from '../../containers/user_profiles/api.mock';
describe('useCasesColumns ', () => {

View file

@ -27,8 +27,8 @@ import type { UserProfileWithAvatar } from '@kbn/user-profile-components';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import type { ActionConnector } from '../../../common/types/domain';
import { CaseSeverity, CaseStatuses } from '../../../common/types/domain';
import type { CaseUI } from '../../../common/ui/types';
import { CaseStatuses, CaseSeverity } from '../../../common/api';
import { OWNER_INFO } from '../../../common/constants';
import { getEmptyTagValue } from '../empty_value';
import { FormattedRelativePreferenceDate } from '../formatted_date';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { CaseStatuses } from '../../../common/api';
import { CaseStatuses } from '../../../common/types/domain';
import { basicCase } from '../../containers/mock';
import { getStatusDate, getStatusTitle } from './helpers';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { CaseStatuses } from '../../../common/api';
import { CaseStatuses } from '../../../common/types/domain';
import type { CaseUI } from '../../containers/types';
import { statuses } from '../status';

View file

@ -17,7 +17,7 @@ import {
EuiIconTip,
} from '@elastic/eui';
import type { CaseUI } from '../../../common/ui/types';
import type { CaseStatuses } from '../../../common/api';
import type { CaseStatuses } from '../../../common/types/domain';
import * as i18n from '../case_view/translations';
import { Actions } from './actions';
import { StatusContextMenu } from './status_context_menu';

View file

@ -8,7 +8,7 @@
import React from 'react';
import { mount } from 'enzyme';
import { CaseStatuses } from '../../../common/api';
import { CaseStatuses } from '../../../common/types/domain';
import { StatusContextMenu } from './status_context_menu';
describe('SyncAlertsSwitch', () => {

View file

@ -8,8 +8,8 @@
import React, { memo, useCallback, useMemo, useState } from 'react';
import { EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui';
import { Status } from '@kbn/cases-components/src/status/status';
import type { CaseStatuses } from '../../../common/api';
import { caseStatuses } from '../../../common/api';
import type { CaseStatuses } from '../../../common/types/domain';
import { caseStatuses } from '../../../common/types/domain';
import { StatusPopoverButton } from '../status';
import { CHANGE_STATUS } from '../all_cases/translations';

View file

@ -16,9 +16,9 @@ import {
} from '@elastic/eui';
import type { UserProfileWithAvatar } from '@kbn/user-profile-components';
import type { CaseAssignees } from '../../../../common/types/domain';
import type { CasesPermissions } from '../../../../common';
import { useAssignees } from '../../../containers/user_profiles/use_assignees';
import type { CaseAssignees } from '../../../../common/api/cases/assignee';
import * as i18n from '../translations';
import { SidebarTitle } from './sidebar_title';
import { RemovableUser } from '../../user_profiles/removable_user';

View file

@ -16,9 +16,9 @@ import { useGetCaseConnectors } from '../../../containers/use_get_case_connector
import { useCasesFeatures } from '../../../common/use_cases_features';
import { useGetCurrentUserProfile } from '../../../containers/user_profiles/use_get_current_user_profile';
import { useGetSupportedActionConnectors } from '../../../containers/configure/use_get_supported_action_connectors';
import type { CaseSeverity } from '../../../../common/api';
import type { CaseSeverity, CaseStatuses } from '../../../../common/types/domain';
import type { CaseUsers, UseFetchAlertData } from '../../../../common/ui/types';
import type { CaseUI, CaseStatuses } from '../../../../common';
import type { CaseUI } from '../../../../common';
import { EditConnector } from '../../edit_connector';
import type { CasesNavigation } from '../../links';
import { StatusActionButton } from '../../status/button';

View file

@ -5,12 +5,12 @@
* 2.0.
*/
import { CommentType } from '../../../../common/api';
import type { CommentUI } from '../../../containers/types';
import { AttachmentType } from '../../../../common/types/domain';
import type { AttachmentUI } from '../../../containers/types';
export const getManualAlertIds = (comments: CommentUI[]): string[] => {
const dedupeAlerts = comments.reduce((alertIds, comment: CommentUI) => {
if (comment.type === CommentType.alert) {
export const getManualAlertIds = (comments: AttachmentUI[]): string[] => {
const dedupeAlerts = comments.reduce((alertIds, comment: AttachmentUI) => {
if (comment.type === AttachmentType.alert) {
const ids = Array.isArray(comment.alertId) ? comment.alertId : [comment.alertId];
ids.forEach((id) => alertIds.add(id));
return alertIds;
@ -20,25 +20,28 @@ export const getManualAlertIds = (comments: CommentUI[]): string[] => {
return Array.from(dedupeAlerts);
};
export const getRegistrationContextFromAlerts = (comments: CommentUI[]): string[] => {
const dedupeRegistrationContext = comments.reduce((registrationContexts, comment: CommentUI) => {
if (comment.type === CommentType.alert) {
const indices = Array.isArray(comment.index) ? comment.index : [comment.index];
indices.forEach((index) => {
// That's legacy code, we created some index alias so everything should work as expected
if (index.startsWith('.siem-signals')) {
registrationContexts.add('security');
} else {
const registrationContext = getRegistrationContextFromIndex(index);
if (registrationContext) {
registrationContexts.add(registrationContext);
export const getRegistrationContextFromAlerts = (comments: AttachmentUI[]): string[] => {
const dedupeRegistrationContext = comments.reduce(
(registrationContexts, comment: AttachmentUI) => {
if (comment.type === AttachmentType.alert) {
const indices = Array.isArray(comment.index) ? comment.index : [comment.index];
indices.forEach((index) => {
// That's legacy code, we created some index alias so everything should work as expected
if (index.startsWith('.siem-signals')) {
registrationContexts.add('security');
} else {
const registrationContext = getRegistrationContextFromIndex(index);
if (registrationContext) {
registrationContexts.add(registrationContext);
}
}
}
});
});
return registrationContexts;
}
return registrationContexts;
}
return registrationContexts;
}, new Set<string>());
},
new Set<string>()
);
return Array.from(dedupeRegistrationContext);
};

View file

@ -8,9 +8,7 @@
import { useCallback, useState } from 'react';
import deepEqual from 'fast-deep-equal';
import type { CaseConnector } from '../../../common/types/domain';
import type { CaseAttributes } from '../../../common/api/cases/case';
import type { CaseStatuses } from '../../../common/api/cases/status';
import type { CaseStatuses, CaseAttributes, CaseConnector } from '../../../common/types/domain';
import type { CaseUI, UpdateByKey, UpdateKey } from '../../containers/types';
import { useUpdateCase } from '../../containers/use_update_case';
import { getTypedPayload } from '../../containers/utils';

Some files were not shown because too many files have changed in this diff Show more