mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Security Solution][Case] Case action type (#80870)
* Init connector * Add test * Improve comment type * Add integration tests * Fix i18n * Improve tests * Show unknown when username is null * Improve comment type * Pass connector to case client * Improve type after PR #82125 * Add comment migration test * Fix integration tests * Fix reporter on table * Create case connector ui * Add connector to README * Improve casting on executor * Translate name * Improve test * Create comment type enum * Fix type * Fix i18n * Move README to cases * Filter out case connector from alerting Co-authored-by: Mike Côté <mikecote@users.noreply.github.com> Co-authored-by: Mike Côté <mikecote@users.noreply.github.com>
This commit is contained in:
parent
57f74012b1
commit
7abb1e3033
60 changed files with 2621 additions and 151 deletions
|
@ -9,6 +9,7 @@
|
|||
"xpack.apm": "plugins/apm",
|
||||
"xpack.beatsManagement": "plugins/beats_management",
|
||||
"xpack.canvas": "plugins/canvas",
|
||||
"xpack.case": "plugins/case",
|
||||
"xpack.cloud": "plugins/cloud",
|
||||
"xpack.dashboard": "plugins/dashboard_enhanced",
|
||||
"xpack.discover": "plugins/discover_enhanced",
|
||||
|
|
|
@ -724,4 +724,4 @@ Instead of `schema.maybe()`, use `schema.nullable()`, which is the same as `sche
|
|||
|
||||
## user interface
|
||||
|
||||
In order to make this action usable in the Kibana UI, you will need to provide all the UI editing aspects of the action. The existing action type user interfaces are defined in [`x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types`](../triggers_actions_ui/public/application/components/builtin_action_types). For more information, see the [UI documentation](../triggers_actions_ui/README.md#create-and-register-new-action-type-ui).
|
||||
In order to make this action usable in the Kibana UI, you will need to provide all the UI editing aspects of the action. The existing action type user interfaces are defined in [`x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types`](../triggers_actions_ui/public/application/components/builtin_action_types). For more information, see the [UI documentation](../triggers_actions_ui/README.md#create-and-register-new-action-type-ui).
|
|
@ -7,3 +7,91 @@ Elastic is developing a Case Management Workflow. Follow our progress:
|
|||
- [Case API Documentation](https://documenter.getpostman.com/view/172706/SW7c2SuF?version=latest)
|
||||
- [Github Meta](https://github.com/elastic/kibana/issues/50103)
|
||||
|
||||
|
||||
# Action types
|
||||
|
||||
|
||||
See [Kibana Actions](https://github.com/elastic/kibana/tree/master/x-pack/plugins/actions) for more information.
|
||||
|
||||
## Case
|
||||
|
||||
ID: `.case`
|
||||
|
||||
The params properties are modelled after the arguments to the [Cases API](https://www.elastic.co/guide/en/security/master/cases-api-overview.html).
|
||||
|
||||
### `config`
|
||||
|
||||
This action has no `config` properties.
|
||||
|
||||
### `secrets`
|
||||
|
||||
This action type has no `secrets` properties.
|
||||
|
||||
### `params`
|
||||
|
||||
| Property | Description | Type |
|
||||
| --------------- | ------------------------------------------------------------------------- | ------ |
|
||||
| subAction | The sub action to perform. It can be `create`, `update`, and `addComment` | string |
|
||||
| subActionParams | The parameters of the sub action | object |
|
||||
|
||||
#### `subActionParams (create)`
|
||||
|
||||
| Property | Description | Type |
|
||||
| ----------- | --------------------------------------------------------------------- | ----------------------- |
|
||||
| tile | The case’s title. | string |
|
||||
| description | The case’s description. | string |
|
||||
| tags | String array containing words and phrases that help categorize cases. | string[] |
|
||||
| connector | Object containing the connector’s configuration. | [connector](#connector) |
|
||||
|
||||
#### `subActionParams (update)`
|
||||
|
||||
| Property | Description | Type |
|
||||
| ----------- | ---------------------------------------------------------- | ----------------------- |
|
||||
| id | The ID of the case being updated. | string |
|
||||
| tile | The updated case title. | string |
|
||||
| description | The updated case description. | string |
|
||||
| tags | The updated case tags. | string |
|
||||
| connector | Object containing the connector’s configuration. | [connector](#connector) |
|
||||
| status | The updated case status, which can be: `open` or `closed`. | string |
|
||||
| version | The current case version. | string |
|
||||
|
||||
#### `subActionParams (addComment)`
|
||||
|
||||
| Property | Description | Type |
|
||||
| -------- | --------------------------------------------------------- | ------ |
|
||||
| comment | The case’s new comment. | string |
|
||||
| type | The type of the comment, which can be: `user` or `alert`. | string |
|
||||
|
||||
#### `connector`
|
||||
|
||||
| Property | Description | Type |
|
||||
| -------- | ------------------------------------------------------------------------------------------------- | ----------------- |
|
||||
| id | ID of the connector used for pushing case updates to external systems. | string |
|
||||
| name | The connector name. | string |
|
||||
| type | The type of the connector. Must be one of these: `.servicenow`, `jira`, `.resilient`, and `.none` | string |
|
||||
| fields | Object containing the connector’s fields. | [fields](#fields) |
|
||||
|
||||
#### `fields`
|
||||
|
||||
For ServiceNow connectors:
|
||||
|
||||
| Property | Description | Type |
|
||||
| -------- | ----------------------------- | ------ |
|
||||
| urgency | The urgency of the incident. | string |
|
||||
| severity | The severity of the incident. | string |
|
||||
| impact | The impact of the incident. | string |
|
||||
|
||||
For Jira connectors:
|
||||
|
||||
| Property | Description | Type |
|
||||
| --------- | -------------------------------------------------------------------- | ------ |
|
||||
| issueType | The issue type of the issue. | string |
|
||||
| priority | The priority of the issue. | string |
|
||||
| parent | The key of the parent issue (Valid when the issue type is Sub-task). | string |
|
||||
|
||||
For IBM Resilient connectors:
|
||||
|
||||
| Property | Description | Type |
|
||||
| ------------ | ------------------------------- | -------- |
|
||||
| issueTypes | The issue types of the issue. | string[] |
|
||||
| severityCode | The severity code of the issue. | string |
|
||||
|
|
|
@ -10,6 +10,7 @@ import { UserRT } from '../user';
|
|||
|
||||
const CommentBasicRt = rt.type({
|
||||
comment: rt.string,
|
||||
type: rt.union([rt.literal('alert'), rt.literal('user')]),
|
||||
});
|
||||
|
||||
export const CommentAttributesRt = rt.intersection([
|
||||
|
@ -37,7 +38,7 @@ export const CommentResponseRt = rt.intersection([
|
|||
export const AllCommentsResponseRT = rt.array(CommentResponseRt);
|
||||
|
||||
export const CommentPatchRequestRt = rt.intersection([
|
||||
rt.partial(CommentRequestRt.props),
|
||||
rt.partial(CommentBasicRt.props),
|
||||
rt.type({ id: rt.string, version: rt.string }),
|
||||
]);
|
||||
|
||||
|
@ -48,6 +49,11 @@ export const CommentsResponseRt = rt.type({
|
|||
total: rt.number,
|
||||
});
|
||||
|
||||
export enum CommentType {
|
||||
user = 'user',
|
||||
alert = 'alert',
|
||||
}
|
||||
|
||||
export const AllCommentsResponseRt = rt.array(CommentResponseRt);
|
||||
|
||||
export type CommentAttributes = rt.TypeOf<typeof CommentAttributesRt>;
|
||||
|
|
|
@ -180,7 +180,7 @@ describe('create', () => {
|
|||
|
||||
describe('unhappy path', () => {
|
||||
test('it throws when missing title', async () => {
|
||||
expect.assertions(1);
|
||||
expect.assertions(3);
|
||||
const postCase = {
|
||||
description: 'This is a brand new case of a bad meanie defacing data',
|
||||
tags: ['defacement'],
|
||||
|
@ -199,11 +199,15 @@ describe('create', () => {
|
|||
caseClient.client
|
||||
// @ts-expect-error
|
||||
.create({ theCase: postCase })
|
||||
.catch((e) => expect(e).not.toBeNull());
|
||||
.catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
expect(e.output.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
test('it throws when missing description', async () => {
|
||||
expect.assertions(1);
|
||||
expect.assertions(3);
|
||||
const postCase = {
|
||||
title: 'a title',
|
||||
tags: ['defacement'],
|
||||
|
@ -222,11 +226,15 @@ describe('create', () => {
|
|||
caseClient.client
|
||||
// @ts-expect-error
|
||||
.create({ theCase: postCase })
|
||||
.catch((e) => expect(e).not.toBeNull());
|
||||
.catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
expect(e.output.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
test('it throws when missing tags', async () => {
|
||||
expect.assertions(1);
|
||||
expect.assertions(3);
|
||||
const postCase = {
|
||||
title: 'a title',
|
||||
description: 'This is a brand new case of a bad meanie defacing data',
|
||||
|
@ -245,11 +253,15 @@ describe('create', () => {
|
|||
caseClient.client
|
||||
// @ts-expect-error
|
||||
.create({ theCase: postCase })
|
||||
.catch((e) => expect(e).not.toBeNull());
|
||||
.catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
expect(e.output.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
test('it throws when missing connector ', async () => {
|
||||
expect.assertions(1);
|
||||
expect.assertions(3);
|
||||
const postCase = {
|
||||
title: 'a title',
|
||||
description: 'This is a brand new case of a bad meanie defacing data',
|
||||
|
@ -263,11 +275,15 @@ describe('create', () => {
|
|||
caseClient.client
|
||||
// @ts-expect-error
|
||||
.create({ theCase: postCase })
|
||||
.catch((e) => expect(e).not.toBeNull());
|
||||
.catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
expect(e.output.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
test('it throws when connector missing the right fields', async () => {
|
||||
expect.assertions(1);
|
||||
expect.assertions(3);
|
||||
const postCase = {
|
||||
title: 'a title',
|
||||
description: 'This is a brand new case of a bad meanie defacing data',
|
||||
|
@ -287,11 +303,15 @@ describe('create', () => {
|
|||
caseClient.client
|
||||
// @ts-expect-error
|
||||
.create({ theCase: postCase })
|
||||
.catch((e) => expect(e).not.toBeNull());
|
||||
.catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
expect(e.output.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
test('it throws if you passing status for a new case', async () => {
|
||||
expect.assertions(1);
|
||||
expect.assertions(3);
|
||||
const postCase = {
|
||||
title: 'a title',
|
||||
description: 'This is a brand new case of a bad meanie defacing data',
|
||||
|
@ -309,7 +329,11 @@ describe('create', () => {
|
|||
caseSavedObject: mockCases,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
caseClient.client.create({ theCase: postCase }).catch((e) => expect(e).not.toBeNull());
|
||||
caseClient.client.create({ theCase: postCase }).catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
expect(e.output.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
it(`Returns an error if postNewCase throws`, async () => {
|
||||
|
@ -329,7 +353,11 @@ describe('create', () => {
|
|||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
|
||||
caseClient.client.create({ theCase: postCase }).catch((e) => expect(e).not.toBeNull());
|
||||
caseClient.client.create({ theCase: postCase }).catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
expect(e.output.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -247,7 +247,7 @@ describe('update', () => {
|
|||
|
||||
describe('unhappy path', () => {
|
||||
test('it throws when missing id', async () => {
|
||||
expect.assertions(1);
|
||||
expect.assertions(3);
|
||||
const patchCases = {
|
||||
cases: [
|
||||
{
|
||||
|
@ -270,11 +270,15 @@ describe('update', () => {
|
|||
caseClient.client
|
||||
// @ts-expect-error
|
||||
.update({ cases: patchCases })
|
||||
.catch((e) => expect(e).not.toBeNull());
|
||||
.catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
expect(e.output.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
test('it throws when missing version', async () => {
|
||||
expect.assertions(1);
|
||||
expect.assertions(3);
|
||||
const patchCases = {
|
||||
cases: [
|
||||
{
|
||||
|
@ -297,11 +301,15 @@ describe('update', () => {
|
|||
caseClient.client
|
||||
// @ts-expect-error
|
||||
.update({ cases: patchCases })
|
||||
.catch((e) => expect(e).not.toBeNull());
|
||||
.catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
expect(e.output.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
test('it throws when fields are identical', async () => {
|
||||
expect.assertions(1);
|
||||
expect.assertions(4);
|
||||
const patchCases = {
|
||||
cases: [
|
||||
{
|
||||
|
@ -317,14 +325,16 @@ describe('update', () => {
|
|||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
caseClient.client
|
||||
.update({ cases: patchCases })
|
||||
.catch((e) =>
|
||||
expect(e.message).toBe('All update fields are identical to current version.')
|
||||
);
|
||||
caseClient.client.update({ cases: patchCases }).catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
expect(e.output.statusCode).toBe(406);
|
||||
expect(e.message).toBe('All update fields are identical to current version.');
|
||||
});
|
||||
});
|
||||
|
||||
test('it throws when case does not exist', async () => {
|
||||
expect.assertions(4);
|
||||
const patchCases = {
|
||||
cases: [
|
||||
{
|
||||
|
@ -345,17 +355,18 @@ describe('update', () => {
|
|||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
caseClient.client
|
||||
.update({ cases: patchCases })
|
||||
.catch((e) =>
|
||||
expect(e.message).toBe(
|
||||
'These cases not-exists do not exist. Please check you have the correct ids.'
|
||||
)
|
||||
caseClient.client.update({ cases: patchCases }).catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
expect(e.output.statusCode).toBe(404);
|
||||
expect(e.message).toBe(
|
||||
'These cases not-exists do not exist. Please check you have the correct ids.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('it throws when cases conflicts', async () => {
|
||||
expect.assertions(1);
|
||||
expect.assertions(4);
|
||||
const patchCases = {
|
||||
cases: [
|
||||
{
|
||||
|
@ -371,13 +382,14 @@ describe('update', () => {
|
|||
});
|
||||
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
caseClient.client
|
||||
.update({ cases: patchCases })
|
||||
.catch((e) =>
|
||||
expect(e.message).toBe(
|
||||
'These cases mock-id-1 has been updated. Please refresh before saving additional updates.'
|
||||
)
|
||||
caseClient.client.update({ cases: patchCases }).catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
expect(e.output.statusCode).toBe(409);
|
||||
expect(e.message).toBe(
|
||||
'These cases mock-id-1 has been updated. Please refresh before saving additional updates.'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CommentType } from '../../../common/api';
|
||||
import {
|
||||
createMockSavedObjectsRepository,
|
||||
mockCaseComments,
|
||||
|
@ -30,13 +31,14 @@ describe('addComment', () => {
|
|||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const res = await caseClient.client.addComment({
|
||||
caseId: 'mock-id-1',
|
||||
comment: { comment: 'Wow, good luck catching that bad meanie!' },
|
||||
comment: { comment: 'Wow, good luck catching that bad meanie!', type: CommentType.user },
|
||||
});
|
||||
|
||||
expect(res.id).toEqual('mock-id-1');
|
||||
expect(res.totalComment).toEqual(res.comments!.length);
|
||||
expect(res.comments![res.comments!.length - 1]).toEqual({
|
||||
comment: 'Wow, good luck catching that bad meanie!',
|
||||
type: CommentType.user,
|
||||
created_at: '2020-10-23T21:54:48.952Z',
|
||||
created_by: {
|
||||
email: 'd00d@awesome.com',
|
||||
|
@ -61,7 +63,7 @@ describe('addComment', () => {
|
|||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
const res = await caseClient.client.addComment({
|
||||
caseId: 'mock-id-1',
|
||||
comment: { comment: 'Wow, good luck catching that bad meanie!' },
|
||||
comment: { comment: 'Wow, good luck catching that bad meanie!', type: CommentType.user },
|
||||
});
|
||||
|
||||
expect(res.updated_at).toEqual('2020-10-23T21:54:48.952Z');
|
||||
|
@ -81,7 +83,7 @@ describe('addComment', () => {
|
|||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
await caseClient.client.addComment({
|
||||
caseId: 'mock-id-1',
|
||||
comment: { comment: 'Wow, good luck catching that bad meanie!' },
|
||||
comment: { comment: 'Wow, good luck catching that bad meanie!', type: CommentType.user },
|
||||
});
|
||||
|
||||
expect(
|
||||
|
@ -125,12 +127,13 @@ describe('addComment', () => {
|
|||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient, true);
|
||||
const res = await caseClient.client.addComment({
|
||||
caseId: 'mock-id-1',
|
||||
comment: { comment: 'Wow, good luck catching that bad meanie!' },
|
||||
comment: { comment: 'Wow, good luck catching that bad meanie!', type: CommentType.user },
|
||||
});
|
||||
|
||||
expect(res.id).toEqual('mock-id-1');
|
||||
expect(res.comments![res.comments!.length - 1]).toEqual({
|
||||
comment: 'Wow, good luck catching that bad meanie!',
|
||||
type: CommentType.user,
|
||||
created_at: '2020-10-23T21:54:48.952Z',
|
||||
created_by: {
|
||||
email: null,
|
||||
|
@ -169,6 +172,27 @@ describe('addComment', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('it throws when missing comment type', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const savedObjectsClient = createMockSavedObjectsRepository({
|
||||
caseSavedObject: mockCases,
|
||||
caseCommentSavedObject: mockCaseComments,
|
||||
});
|
||||
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
|
||||
caseClient.client
|
||||
.addComment({
|
||||
caseId: 'mock-id-1',
|
||||
// @ts-expect-error
|
||||
comment: { comment: 'a comment' },
|
||||
})
|
||||
.catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.isBoom).toBe(true);
|
||||
expect(e.output.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
test('it throws when the case does not exists', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
|
@ -180,7 +204,7 @@ describe('addComment', () => {
|
|||
caseClient.client
|
||||
.addComment({
|
||||
caseId: 'not-exists',
|
||||
comment: { comment: 'Wow, good luck catching that bad meanie!' },
|
||||
comment: { comment: 'Wow, good luck catching that bad meanie!', type: CommentType.user },
|
||||
})
|
||||
.catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
|
@ -200,7 +224,7 @@ describe('addComment', () => {
|
|||
caseClient.client
|
||||
.addComment({
|
||||
caseId: 'mock-id-1',
|
||||
comment: { comment: 'Throw an error' },
|
||||
comment: { comment: 'Throw an error', type: CommentType.user },
|
||||
})
|
||||
.catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
|
|
891
x-pack/plugins/case/server/connectors/case/index.test.ts
Normal file
891
x-pack/plugins/case/server/connectors/case/index.test.ts
Normal file
|
@ -0,0 +1,891 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Logger } from '../../../../../../src/core/server';
|
||||
import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
|
||||
import { actionsMock } from '../../../../actions/server/mocks';
|
||||
import { validateParams } from '../../../../actions/server/lib';
|
||||
import { ConnectorTypes, CommentType } from '../../../common/api';
|
||||
import {
|
||||
createCaseServiceMock,
|
||||
createConfigureServiceMock,
|
||||
createUserActionServiceMock,
|
||||
} from '../../services/mocks';
|
||||
import { CaseActionType, CaseActionTypeExecutorOptions, CaseExecutorParams } from './types';
|
||||
import { getActionType } from '.';
|
||||
import { createCaseClientMock } from '../../client/mocks';
|
||||
|
||||
const mockCaseClient = createCaseClientMock();
|
||||
|
||||
jest.mock('../../client', () => ({
|
||||
createCaseClient: () => mockCaseClient,
|
||||
}));
|
||||
|
||||
const services = actionsMock.createServices();
|
||||
let caseActionType: CaseActionType;
|
||||
|
||||
describe('case connector', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
const logger = loggingSystemMock.create().get() as jest.Mocked<Logger>;
|
||||
const caseService = createCaseServiceMock();
|
||||
const caseConfigureService = createConfigureServiceMock();
|
||||
const userActionService = createUserActionServiceMock();
|
||||
caseActionType = getActionType({
|
||||
logger,
|
||||
caseService,
|
||||
caseConfigureService,
|
||||
userActionService,
|
||||
});
|
||||
});
|
||||
|
||||
describe('params validation', () => {
|
||||
describe('create', () => {
|
||||
it('succeeds when params is valid', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'Yo fields!!',
|
||||
connector: {
|
||||
id: 'jira',
|
||||
name: 'Jira',
|
||||
type: '.jira',
|
||||
fields: {
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(validateParams(caseActionType, params)).toEqual(params);
|
||||
});
|
||||
|
||||
it('fails when params is not valid', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'create',
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
validateParams(caseActionType, params);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
describe('connector', () => {
|
||||
const connectorTests = [
|
||||
{
|
||||
test: 'jira',
|
||||
params: {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'Yo fields!!',
|
||||
connector: {
|
||||
id: 'jira',
|
||||
name: 'Jira',
|
||||
type: '.jira',
|
||||
fields: {
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: 'resilient',
|
||||
params: {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'Yo fields!!',
|
||||
connector: {
|
||||
id: 'resilient',
|
||||
name: 'Resilient',
|
||||
type: '.resilient',
|
||||
fields: {
|
||||
incidentTypes: ['13'],
|
||||
severityCode: '3',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: 'servicenow',
|
||||
params: {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'Yo fields!!',
|
||||
connector: {
|
||||
id: 'servicenow',
|
||||
name: 'Servicenow',
|
||||
type: '.servicenow',
|
||||
fields: {
|
||||
impact: 'Medium',
|
||||
severity: 'Medium',
|
||||
urgency: 'Medium',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: 'none',
|
||||
params: {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'Yo fields!!',
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'None',
|
||||
type: '.none',
|
||||
fields: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
connectorTests.forEach(({ params, test }) => {
|
||||
it(`succeeds when ${test} fields are valid`, () => {
|
||||
expect(validateParams(caseActionType, params)).toEqual(params);
|
||||
});
|
||||
});
|
||||
|
||||
it('set fields to null if they are missing', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'Yo fields!!',
|
||||
connector: {
|
||||
id: 'servicenow',
|
||||
name: 'Servicenow',
|
||||
type: '.servicenow',
|
||||
fields: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(validateParams(caseActionType, params)).toEqual({
|
||||
...params,
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'Yo fields!!',
|
||||
connector: {
|
||||
id: 'servicenow',
|
||||
name: 'Servicenow',
|
||||
type: '.servicenow',
|
||||
fields: { impact: null, severity: null, urgency: null },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds when none fields are valid', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'Yo fields!!',
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'None',
|
||||
type: '.none',
|
||||
fields: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(validateParams(caseActionType, params)).toEqual(params);
|
||||
});
|
||||
|
||||
it('fails when issueType is not provided', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'Yo fields!!',
|
||||
connector: {
|
||||
id: 'jira',
|
||||
name: 'Jira',
|
||||
type: '.jira',
|
||||
fields: {
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
validateParams(caseActionType, params);
|
||||
}).toThrow(
|
||||
'[0.subActionParams.connector.fields.issueType]: expected value of type [string] but got [undefined]'
|
||||
);
|
||||
});
|
||||
|
||||
it('fails with excess fields', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'Yo fields!!',
|
||||
connector: {
|
||||
id: 'servicenow',
|
||||
name: 'Servicenow',
|
||||
type: '.servicenow',
|
||||
fields: {
|
||||
impact: 'Medium',
|
||||
severity: 'Medium',
|
||||
urgency: 'Medium',
|
||||
excess: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
validateParams(caseActionType, params);
|
||||
}).toThrow(
|
||||
'[0.subActionParams.connector.fields.excess]: definition for this key is missing'
|
||||
);
|
||||
});
|
||||
|
||||
it('fails with valid fields but wrong type', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'Yo fields!!',
|
||||
connector: {
|
||||
id: 'resilient',
|
||||
name: 'Resilient',
|
||||
type: '.resilient',
|
||||
fields: {
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
validateParams(caseActionType, params);
|
||||
}).toThrow(
|
||||
'[0.subActionParams.connector.fields.issueType]: definition for this key is missing'
|
||||
);
|
||||
});
|
||||
|
||||
it('fails when fields are not null and the type is none', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'Yo fields!!',
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'None',
|
||||
type: '.none',
|
||||
fields: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
validateParams(caseActionType, params);
|
||||
}).toThrow(
|
||||
'[0.subActionParams.connector]: Fields must be set to null for connectors of type .none'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('succeeds when params is valid', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
id: 'case-id',
|
||||
version: '123',
|
||||
title: 'Update title',
|
||||
},
|
||||
};
|
||||
|
||||
expect(validateParams(caseActionType, params)).toEqual({
|
||||
...params,
|
||||
subActionParams: {
|
||||
description: null,
|
||||
tags: null,
|
||||
title: null,
|
||||
status: null,
|
||||
connector: null,
|
||||
...(params.subActionParams as Record<string, unknown>),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('connector', () => {
|
||||
it('succeeds when jira fields are valid', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
id: 'case-id',
|
||||
version: '123',
|
||||
connector: {
|
||||
id: 'jira',
|
||||
name: 'Jira',
|
||||
type: '.jira',
|
||||
fields: {
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(validateParams(caseActionType, params)).toEqual({
|
||||
...params,
|
||||
subActionParams: {
|
||||
description: null,
|
||||
tags: null,
|
||||
title: null,
|
||||
status: null,
|
||||
...(params.subActionParams as Record<string, unknown>),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds when resilient fields are valid', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
id: 'case-id',
|
||||
version: '123',
|
||||
connector: {
|
||||
id: 'resilient',
|
||||
name: 'Resilient',
|
||||
type: '.resilient',
|
||||
fields: {
|
||||
incidentTypes: ['13'],
|
||||
severityCode: '3',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(validateParams(caseActionType, params)).toEqual({
|
||||
...params,
|
||||
subActionParams: {
|
||||
description: null,
|
||||
tags: null,
|
||||
title: null,
|
||||
status: null,
|
||||
...(params.subActionParams as Record<string, unknown>),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds when servicenow fields are valid', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
id: 'case-id',
|
||||
version: '123',
|
||||
connector: {
|
||||
id: 'servicenow',
|
||||
name: 'Servicenow',
|
||||
type: '.servicenow',
|
||||
fields: {
|
||||
impact: 'Medium',
|
||||
severity: 'Medium',
|
||||
urgency: 'Medium',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(validateParams(caseActionType, params)).toEqual({
|
||||
...params,
|
||||
subActionParams: {
|
||||
description: null,
|
||||
tags: null,
|
||||
title: null,
|
||||
status: null,
|
||||
...(params.subActionParams as Record<string, unknown>),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('set fields to null if they are missing', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
id: 'case-id',
|
||||
version: '123',
|
||||
connector: {
|
||||
id: 'servicenow',
|
||||
name: 'Servicenow',
|
||||
type: '.servicenow',
|
||||
fields: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(validateParams(caseActionType, params)).toEqual({
|
||||
...params,
|
||||
subActionParams: {
|
||||
id: 'case-id',
|
||||
version: '123',
|
||||
description: null,
|
||||
tags: null,
|
||||
title: null,
|
||||
status: null,
|
||||
connector: {
|
||||
id: 'servicenow',
|
||||
name: 'Servicenow',
|
||||
type: '.servicenow',
|
||||
fields: { impact: null, severity: null, urgency: null },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds when none fields are valid', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
id: 'case-id',
|
||||
version: '123',
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'None',
|
||||
type: '.none',
|
||||
fields: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(validateParams(caseActionType, params)).toEqual({
|
||||
...params,
|
||||
subActionParams: {
|
||||
description: null,
|
||||
tags: null,
|
||||
title: null,
|
||||
status: null,
|
||||
...(params.subActionParams as Record<string, unknown>),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('fails when issueType is not provided', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
id: 'case-id',
|
||||
version: '123',
|
||||
connector: {
|
||||
id: 'jira',
|
||||
name: 'Jira',
|
||||
type: '.jira',
|
||||
fields: {
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
validateParams(caseActionType, params);
|
||||
}).toThrow(
|
||||
'[subActionParams.connector.0.fields.issueType]: expected value of type [string] but got [undefined]'
|
||||
);
|
||||
});
|
||||
|
||||
it('fails with excess fields', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
id: 'case-id',
|
||||
version: '123',
|
||||
connector: {
|
||||
id: 'servicenow',
|
||||
name: 'Servicenow',
|
||||
type: '.servicenow',
|
||||
fields: {
|
||||
impact: 'Medium',
|
||||
severity: 'Medium',
|
||||
urgency: 'Medium',
|
||||
excess: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
validateParams(caseActionType, params);
|
||||
}).toThrow(
|
||||
'[subActionParams.connector.0.fields.excess]: definition for this key is missing'
|
||||
);
|
||||
});
|
||||
|
||||
it('fails with valid fields but wrong type', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
id: 'case-id',
|
||||
version: '123',
|
||||
connector: {
|
||||
id: 'resilient',
|
||||
name: 'Resilient',
|
||||
type: '.resilient',
|
||||
fields: {
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
validateParams(caseActionType, params);
|
||||
}).toThrow(
|
||||
'[subActionParams.connector.0.fields.issueType]: definition for this key is missing'
|
||||
);
|
||||
});
|
||||
|
||||
it('fails when fields are not null and the type is none', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
id: 'case-id',
|
||||
version: '123',
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'None',
|
||||
type: '.none',
|
||||
fields: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
validateParams(caseActionType, params);
|
||||
}).toThrow(
|
||||
'[subActionParams.connector.0]: Fields must be set to null for connectors of type .none'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('fails when params is not valid', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'update',
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
validateParams(caseActionType, params);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('add comment', () => {
|
||||
it('succeeds when params is valid', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'addComment',
|
||||
subActionParams: {
|
||||
caseId: 'case-id',
|
||||
comment: { comment: 'a comment', type: CommentType.user },
|
||||
},
|
||||
};
|
||||
|
||||
expect(validateParams(caseActionType, params)).toEqual(params);
|
||||
});
|
||||
|
||||
it('fails when params is not valid', () => {
|
||||
const params: Record<string, unknown> = {
|
||||
subAction: 'addComment',
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
validateParams(caseActionType, params);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('execute', () => {
|
||||
it('allows only supported sub-actions', async () => {
|
||||
expect.assertions(2);
|
||||
const actionId = 'some-id';
|
||||
const params: CaseExecutorParams = {
|
||||
// @ts-expect-error
|
||||
subAction: 'not-supported',
|
||||
// @ts-expect-error
|
||||
subActionParams: {},
|
||||
};
|
||||
|
||||
const executorOptions: CaseActionTypeExecutorOptions = {
|
||||
actionId,
|
||||
config: {},
|
||||
params,
|
||||
secrets: {},
|
||||
services,
|
||||
};
|
||||
|
||||
caseActionType.executor(executorOptions).catch((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.message).toBe('[Action][Case] subAction not-supported not implemented.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('executes correctly', async () => {
|
||||
const createReturn = {
|
||||
id: 'mock-it',
|
||||
comments: [],
|
||||
totalComment: 0,
|
||||
closed_at: null,
|
||||
closed_by: null,
|
||||
connector: { id: 'none', name: 'none', type: ConnectorTypes.none, fields: null },
|
||||
created_at: '2019-11-25T21:54:48.952Z',
|
||||
created_by: {
|
||||
full_name: 'Awesome D00d',
|
||||
email: 'd00d@awesome.com',
|
||||
username: 'awesome',
|
||||
},
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'Yo fields!!',
|
||||
external_service: null,
|
||||
status: 'open' as const,
|
||||
updated_at: null,
|
||||
updated_by: null,
|
||||
version: 'WzksMV0=',
|
||||
};
|
||||
|
||||
mockCaseClient.create.mockReturnValue(Promise.resolve(createReturn));
|
||||
|
||||
const actionId = 'some-id';
|
||||
const params: CaseExecutorParams = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'Yo fields!!',
|
||||
connector: {
|
||||
id: 'jira',
|
||||
name: 'Jira',
|
||||
type: '.jira',
|
||||
fields: {
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const executorOptions: CaseActionTypeExecutorOptions = {
|
||||
actionId,
|
||||
config: {},
|
||||
params,
|
||||
secrets: {},
|
||||
services,
|
||||
};
|
||||
|
||||
const result = await caseActionType.executor(executorOptions);
|
||||
|
||||
expect(result).toEqual({ actionId, status: 'ok', data: createReturn });
|
||||
expect(mockCaseClient.create).toHaveBeenCalledWith({
|
||||
theCase: {
|
||||
...params.subActionParams,
|
||||
connector: {
|
||||
id: 'jira',
|
||||
name: 'Jira',
|
||||
type: '.jira',
|
||||
fields: {
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('executes correctly', async () => {
|
||||
const updateReturn = [
|
||||
{
|
||||
closed_at: '2019-11-25T21:54:48.952Z',
|
||||
closed_by: {
|
||||
email: 'd00d@awesome.com',
|
||||
full_name: 'Awesome D00d',
|
||||
username: 'awesome',
|
||||
},
|
||||
comments: [],
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'none',
|
||||
type: ConnectorTypes.none,
|
||||
fields: null,
|
||||
},
|
||||
created_at: '2019-11-25T21:54:48.952Z',
|
||||
created_by: {
|
||||
email: 'testemail@elastic.co',
|
||||
full_name: 'elastic',
|
||||
username: 'elastic',
|
||||
},
|
||||
description: 'This is a brand new case of a bad meanie defacing data',
|
||||
id: 'mock-id-1',
|
||||
external_service: null,
|
||||
status: 'open' as const,
|
||||
tags: ['defacement'],
|
||||
title: 'Update title',
|
||||
totalComment: 0,
|
||||
updated_at: '2019-11-25T21:54:48.952Z',
|
||||
updated_by: {
|
||||
email: 'd00d@awesome.com',
|
||||
full_name: 'Awesome D00d',
|
||||
username: 'awesome',
|
||||
},
|
||||
version: 'WzE3LDFd',
|
||||
},
|
||||
];
|
||||
|
||||
mockCaseClient.update.mockReturnValue(Promise.resolve(updateReturn));
|
||||
|
||||
const actionId = 'some-id';
|
||||
const params: CaseExecutorParams = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
id: 'case-id',
|
||||
version: '123',
|
||||
title: 'Update title',
|
||||
description: null,
|
||||
tags: null,
|
||||
status: null,
|
||||
connector: null,
|
||||
},
|
||||
};
|
||||
|
||||
const executorOptions: CaseActionTypeExecutorOptions = {
|
||||
actionId,
|
||||
config: {},
|
||||
params,
|
||||
secrets: {},
|
||||
services,
|
||||
};
|
||||
|
||||
const result = await caseActionType.executor(executorOptions);
|
||||
|
||||
expect(result).toEqual({ actionId, status: 'ok', data: updateReturn });
|
||||
expect(mockCaseClient.update).toHaveBeenCalledWith({
|
||||
// Null values have been striped out.
|
||||
cases: {
|
||||
cases: [
|
||||
{
|
||||
id: 'case-id',
|
||||
version: '123',
|
||||
title: 'Update title',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addComment', () => {
|
||||
it('executes correctly', async () => {
|
||||
const commentReturn = {
|
||||
id: 'mock-it',
|
||||
totalComment: 0,
|
||||
closed_at: null,
|
||||
closed_by: null,
|
||||
connector: { id: 'none', name: 'none', type: ConnectorTypes.none, fields: null },
|
||||
created_at: '2019-11-25T21:54:48.952Z',
|
||||
created_by: { full_name: 'Awesome D00d', email: 'd00d@awesome.com', username: 'awesome' },
|
||||
description: 'This is a brand new case of a bad meanie defacing data',
|
||||
external_service: null,
|
||||
title: 'Super Bad Security Issue',
|
||||
status: 'open' as const,
|
||||
tags: ['defacement'],
|
||||
updated_at: null,
|
||||
updated_by: null,
|
||||
version: 'WzksMV0=',
|
||||
comments: [
|
||||
{
|
||||
comment: 'a comment',
|
||||
type: CommentType.user as const,
|
||||
created_at: '2020-10-23T21:54:48.952Z',
|
||||
created_by: {
|
||||
email: 'd00d@awesome.com',
|
||||
full_name: 'Awesome D00d',
|
||||
username: 'awesome',
|
||||
},
|
||||
id: 'mock-comment',
|
||||
pushed_at: null,
|
||||
pushed_by: null,
|
||||
updated_at: null,
|
||||
updated_by: null,
|
||||
version: 'WzksMV0=',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
mockCaseClient.addComment.mockReturnValue(Promise.resolve(commentReturn));
|
||||
|
||||
const actionId = 'some-id';
|
||||
const params: CaseExecutorParams = {
|
||||
subAction: 'addComment',
|
||||
subActionParams: {
|
||||
caseId: 'case-id',
|
||||
comment: { comment: 'a comment', type: CommentType.user },
|
||||
},
|
||||
};
|
||||
|
||||
const executorOptions: CaseActionTypeExecutorOptions = {
|
||||
actionId,
|
||||
config: {},
|
||||
params,
|
||||
secrets: {},
|
||||
services,
|
||||
};
|
||||
|
||||
const result = await caseActionType.executor(executorOptions);
|
||||
|
||||
expect(result).toEqual({ actionId, status: 'ok', data: commentReturn });
|
||||
expect(mockCaseClient.addComment).toHaveBeenCalledWith({
|
||||
caseId: 'case-id',
|
||||
comment: { comment: 'a comment', type: CommentType.user },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
91
x-pack/plugins/case/server/connectors/case/index.ts
Normal file
91
x-pack/plugins/case/server/connectors/case/index.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { curry } from 'lodash';
|
||||
|
||||
import { KibanaRequest } from 'kibana/server';
|
||||
import { ActionTypeExecutorResult } from '../../../../actions/common';
|
||||
import { CasePatchRequest, CasePostRequest } from '../../../common/api';
|
||||
import { createCaseClient } from '../../client';
|
||||
import { CaseExecutorParamsSchema, CaseConfigurationSchema } from './schema';
|
||||
import {
|
||||
CaseExecutorResponse,
|
||||
ExecutorSubActionAddCommentParams,
|
||||
CaseActionType,
|
||||
CaseActionTypeExecutorOptions,
|
||||
} from './types';
|
||||
import * as i18n from './translations';
|
||||
|
||||
import { GetActionTypeParams } from '..';
|
||||
|
||||
const supportedSubActions: string[] = ['create', 'update', 'addComment'];
|
||||
|
||||
// action type definition
|
||||
export function getActionType({
|
||||
logger,
|
||||
caseService,
|
||||
caseConfigureService,
|
||||
userActionService,
|
||||
}: GetActionTypeParams): CaseActionType {
|
||||
return {
|
||||
id: '.case',
|
||||
minimumLicenseRequired: 'gold',
|
||||
name: i18n.NAME,
|
||||
validate: {
|
||||
config: CaseConfigurationSchema,
|
||||
params: CaseExecutorParamsSchema,
|
||||
},
|
||||
executor: curry(executor)({ logger, caseService, caseConfigureService, userActionService }),
|
||||
};
|
||||
}
|
||||
|
||||
// action executor
|
||||
async function executor(
|
||||
{ logger, caseService, caseConfigureService, userActionService }: GetActionTypeParams,
|
||||
execOptions: CaseActionTypeExecutorOptions
|
||||
): Promise<ActionTypeExecutorResult<CaseExecutorResponse | {}>> {
|
||||
const { actionId, params, services } = execOptions;
|
||||
const { subAction, subActionParams } = params;
|
||||
let data: CaseExecutorResponse | null = null;
|
||||
|
||||
const { savedObjectsClient } = services;
|
||||
const caseClient = createCaseClient({
|
||||
savedObjectsClient,
|
||||
request: {} as KibanaRequest,
|
||||
caseService,
|
||||
caseConfigureService,
|
||||
userActionService,
|
||||
});
|
||||
|
||||
if (!supportedSubActions.includes(subAction)) {
|
||||
const errorMessage = `[Action][Case] subAction ${subAction} not implemented.`;
|
||||
logger.error(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
if (subAction === 'create') {
|
||||
data = await caseClient.create({ theCase: subActionParams as CasePostRequest });
|
||||
}
|
||||
|
||||
if (subAction === 'update') {
|
||||
const updateParamsWithoutNullValues = Object.entries(subActionParams).reduce(
|
||||
(acc, [key, value]) => ({
|
||||
...acc,
|
||||
...(value != null ? { [key]: value } : {}),
|
||||
}),
|
||||
{} as CasePatchRequest
|
||||
);
|
||||
|
||||
data = await caseClient.update({ cases: { cases: [updateParamsWithoutNullValues] } });
|
||||
}
|
||||
|
||||
if (subAction === 'addComment') {
|
||||
const { caseId, comment } = subActionParams as ExecutorSubActionAddCommentParams;
|
||||
data = await caseClient.addComment({ caseId, comment });
|
||||
}
|
||||
|
||||
return { status: 'ok', data: data ?? {}, actionId };
|
||||
}
|
109
x-pack/plugins/case/server/connectors/case/schema.ts
Normal file
109
x-pack/plugins/case/server/connectors/case/schema.ts
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { validateConnector } from './validators';
|
||||
|
||||
// Reserved for future implementation
|
||||
export const CaseConfigurationSchema = schema.object({});
|
||||
|
||||
const CommentProps = {
|
||||
comment: schema.string(),
|
||||
type: schema.oneOf([schema.literal('alert'), schema.literal('user')]),
|
||||
};
|
||||
|
||||
const JiraFieldsSchema = schema.object({
|
||||
issueType: schema.string(),
|
||||
priority: schema.nullable(schema.string()),
|
||||
parent: schema.nullable(schema.string()),
|
||||
});
|
||||
|
||||
const ResilientFieldsSchema = schema.object({
|
||||
incidentTypes: schema.nullable(schema.arrayOf(schema.string())),
|
||||
severityCode: schema.nullable(schema.string()),
|
||||
});
|
||||
|
||||
const ServiceNowFieldsSchema = schema.object({
|
||||
impact: schema.nullable(schema.string()),
|
||||
severity: schema.nullable(schema.string()),
|
||||
urgency: schema.nullable(schema.string()),
|
||||
});
|
||||
|
||||
const NoneFieldsSchema = schema.nullable(schema.object({}));
|
||||
|
||||
const ReducedConnectorFieldsSchema: { [x: string]: any } = {
|
||||
'.jira': JiraFieldsSchema,
|
||||
'.resilient': ResilientFieldsSchema,
|
||||
};
|
||||
|
||||
export const ConnectorProps = {
|
||||
id: schema.string(),
|
||||
name: schema.string(),
|
||||
type: schema.oneOf([
|
||||
schema.literal('.servicenow'),
|
||||
schema.literal('.jira'),
|
||||
schema.literal('.resilient'),
|
||||
schema.literal('.none'),
|
||||
]),
|
||||
// Chain of conditional schemes
|
||||
fields: Object.keys(ReducedConnectorFieldsSchema).reduce(
|
||||
(conditionalSchema, key) =>
|
||||
schema.conditional(
|
||||
schema.siblingRef('type'),
|
||||
key,
|
||||
ReducedConnectorFieldsSchema[key],
|
||||
conditionalSchema
|
||||
),
|
||||
schema.conditional(
|
||||
schema.siblingRef('type'),
|
||||
'.servicenow',
|
||||
ServiceNowFieldsSchema,
|
||||
NoneFieldsSchema
|
||||
)
|
||||
),
|
||||
};
|
||||
|
||||
export const ConnectorSchema = schema.object(ConnectorProps);
|
||||
|
||||
const CaseBasicProps = {
|
||||
description: schema.string(),
|
||||
title: schema.string(),
|
||||
tags: schema.arrayOf(schema.string()),
|
||||
connector: schema.object(ConnectorProps, { validate: validateConnector }),
|
||||
};
|
||||
|
||||
const CaseUpdateRequestProps = {
|
||||
id: schema.string(),
|
||||
version: schema.string(),
|
||||
description: schema.nullable(CaseBasicProps.description),
|
||||
title: schema.nullable(CaseBasicProps.title),
|
||||
tags: schema.nullable(CaseBasicProps.tags),
|
||||
connector: schema.nullable(CaseBasicProps.connector),
|
||||
status: schema.nullable(schema.string()),
|
||||
};
|
||||
|
||||
const CaseAddCommentRequestProps = {
|
||||
caseId: schema.string(),
|
||||
comment: schema.object(CommentProps),
|
||||
};
|
||||
|
||||
export const ExecutorSubActionCreateParamsSchema = schema.object(CaseBasicProps);
|
||||
export const ExecutorSubActionUpdateParamsSchema = schema.object(CaseUpdateRequestProps);
|
||||
export const ExecutorSubActionAddCommentParamsSchema = schema.object(CaseAddCommentRequestProps);
|
||||
|
||||
export const CaseExecutorParamsSchema = schema.oneOf([
|
||||
schema.object({
|
||||
subAction: schema.literal('create'),
|
||||
subActionParams: ExecutorSubActionCreateParamsSchema,
|
||||
}),
|
||||
schema.object({
|
||||
subAction: schema.literal('update'),
|
||||
subActionParams: ExecutorSubActionUpdateParamsSchema,
|
||||
}),
|
||||
schema.object({
|
||||
subAction: schema.literal('addComment'),
|
||||
subActionParams: ExecutorSubActionAddCommentParamsSchema,
|
||||
}),
|
||||
]);
|
11
x-pack/plugins/case/server/connectors/case/translations.ts
Normal file
11
x-pack/plugins/case/server/connectors/case/translations.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const NAME = i18n.translate('xpack.case.connectors.case.title', {
|
||||
defaultMessage: 'Case',
|
||||
});
|
42
x-pack/plugins/case/server/connectors/case/types.ts
Normal file
42
x-pack/plugins/case/server/connectors/case/types.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import { ActionType, ActionTypeExecutorOptions } from '../../../../actions/server';
|
||||
import {
|
||||
CaseExecutorParamsSchema,
|
||||
ExecutorSubActionCreateParamsSchema,
|
||||
ExecutorSubActionUpdateParamsSchema,
|
||||
CaseConfigurationSchema,
|
||||
ExecutorSubActionAddCommentParamsSchema,
|
||||
ConnectorSchema,
|
||||
} from './schema';
|
||||
import { CaseResponse, CasesResponse } from '../../../common/api';
|
||||
|
||||
export type CaseConfiguration = TypeOf<typeof CaseConfigurationSchema>;
|
||||
export type Connector = TypeOf<typeof ConnectorSchema>;
|
||||
|
||||
export type ExecutorSubActionCreateParams = TypeOf<typeof ExecutorSubActionCreateParamsSchema>;
|
||||
export type ExecutorSubActionUpdateParams = TypeOf<typeof ExecutorSubActionUpdateParamsSchema>;
|
||||
export type ExecutorSubActionAddCommentParams = TypeOf<
|
||||
typeof ExecutorSubActionAddCommentParamsSchema
|
||||
>;
|
||||
|
||||
export type CaseExecutorParams = TypeOf<typeof CaseExecutorParamsSchema>;
|
||||
export type CaseExecutorResponse = CaseResponse | CasesResponse;
|
||||
|
||||
export type CaseActionType = ActionType<
|
||||
CaseConfiguration,
|
||||
{},
|
||||
CaseExecutorParams,
|
||||
CaseExecutorResponse | {}
|
||||
>;
|
||||
|
||||
export type CaseActionTypeExecutorOptions = ActionTypeExecutorOptions<
|
||||
CaseConfiguration,
|
||||
{},
|
||||
CaseExecutorParams
|
||||
>;
|
13
x-pack/plugins/case/server/connectors/case/validators.ts
Normal file
13
x-pack/plugins/case/server/connectors/case/validators.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Connector } from './types';
|
||||
|
||||
export const validateConnector = (connector: Connector) => {
|
||||
if (connector.type === '.none' && connector.fields !== null) {
|
||||
return 'Fields must be set to null for connectors of type .none';
|
||||
}
|
||||
};
|
56
x-pack/plugins/case/server/connectors/index.ts
Normal file
56
x-pack/plugins/case/server/connectors/index.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Logger } from 'kibana/server';
|
||||
import {
|
||||
ActionTypeConfig,
|
||||
ActionTypeSecrets,
|
||||
ActionTypeParams,
|
||||
ActionType,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../actions/server/types';
|
||||
import {
|
||||
CaseServiceSetup,
|
||||
CaseConfigureServiceSetup,
|
||||
CaseUserActionServiceSetup,
|
||||
} from '../services';
|
||||
|
||||
import { getActionType as getCaseConnector } from './case';
|
||||
|
||||
export interface GetActionTypeParams {
|
||||
logger: Logger;
|
||||
caseService: CaseServiceSetup;
|
||||
caseConfigureService: CaseConfigureServiceSetup;
|
||||
userActionService: CaseUserActionServiceSetup;
|
||||
}
|
||||
|
||||
export interface RegisterConnectorsArgs extends GetActionTypeParams {
|
||||
actionsRegisterType<
|
||||
Config extends ActionTypeConfig = ActionTypeConfig,
|
||||
Secrets extends ActionTypeSecrets = ActionTypeSecrets,
|
||||
Params extends ActionTypeParams = ActionTypeParams,
|
||||
ExecutorResultData = void
|
||||
>(
|
||||
actionType: ActionType<Config, Secrets, Params, ExecutorResultData>
|
||||
): void;
|
||||
}
|
||||
|
||||
export const registerConnectors = ({
|
||||
actionsRegisterType,
|
||||
logger,
|
||||
caseService,
|
||||
caseConfigureService,
|
||||
userActionService,
|
||||
}: RegisterConnectorsArgs) => {
|
||||
actionsRegisterType(
|
||||
getCaseConnector({
|
||||
logger,
|
||||
caseService,
|
||||
caseConfigureService,
|
||||
userActionService,
|
||||
})
|
||||
);
|
||||
};
|
|
@ -15,6 +15,7 @@ import {
|
|||
import { CoreSetup, CoreStart } from 'src/core/server';
|
||||
|
||||
import { SecurityPluginSetup } from '../../security/server';
|
||||
import { PluginSetupContract as ActionsPluginSetup } from '../../actions/server';
|
||||
import { APP_ID } from '../common/constants';
|
||||
|
||||
import { ConfigType } from './config';
|
||||
|
@ -34,6 +35,7 @@ import {
|
|||
CaseUserActionServiceSetup,
|
||||
} from './services';
|
||||
import { createCaseClient } from './client';
|
||||
import { registerConnectors } from './connectors';
|
||||
|
||||
function createConfig$(context: PluginInitializerContext) {
|
||||
return context.config.create<ConfigType>().pipe(map((config) => config));
|
||||
|
@ -41,6 +43,7 @@ function createConfig$(context: PluginInitializerContext) {
|
|||
|
||||
export interface PluginsSetup {
|
||||
security: SecurityPluginSetup;
|
||||
actions: ActionsPluginSetup;
|
||||
}
|
||||
|
||||
export class CasePlugin {
|
||||
|
@ -94,6 +97,14 @@ export class CasePlugin {
|
|||
userActionService: this.userActionService,
|
||||
router,
|
||||
});
|
||||
|
||||
registerConnectors({
|
||||
actionsRegisterType: plugins.actions.registerType,
|
||||
logger: this.log,
|
||||
caseService: this.caseService,
|
||||
caseConfigureService: this.caseConfigureService,
|
||||
userActionService: this.userActionService,
|
||||
});
|
||||
}
|
||||
|
||||
public async start(core: CoreStart) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
CommentAttributes,
|
||||
ESCaseAttributes,
|
||||
ConnectorTypes,
|
||||
CommentType,
|
||||
} from '../../../../common/api';
|
||||
|
||||
export const mockCases: Array<SavedObject<ESCaseAttributes>> = [
|
||||
|
@ -207,6 +208,7 @@ export const mockCaseComments: Array<SavedObject<CommentAttributes>> = [
|
|||
id: 'mock-comment-1',
|
||||
attributes: {
|
||||
comment: 'Wow, good luck catching that bad meanie!',
|
||||
type: CommentType.user,
|
||||
created_at: '2019-11-25T21:55:00.177Z',
|
||||
created_by: {
|
||||
full_name: 'elastic',
|
||||
|
@ -237,6 +239,7 @@ export const mockCaseComments: Array<SavedObject<CommentAttributes>> = [
|
|||
id: 'mock-comment-2',
|
||||
attributes: {
|
||||
comment: 'Well I decided to update my comment. So what? Deal with it.',
|
||||
type: CommentType.user,
|
||||
created_at: '2019-11-25T21:55:14.633Z',
|
||||
created_by: {
|
||||
full_name: 'elastic',
|
||||
|
@ -268,6 +271,7 @@ export const mockCaseComments: Array<SavedObject<CommentAttributes>> = [
|
|||
id: 'mock-comment-3',
|
||||
attributes: {
|
||||
comment: 'Wow, good luck catching that bad meanie!',
|
||||
type: CommentType.user,
|
||||
created_at: '2019-11-25T22:32:30.608Z',
|
||||
created_by: {
|
||||
full_name: 'elastic',
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
} from '../../__fixtures__';
|
||||
import { initPostCommentApi } from './post_comment';
|
||||
import { CASE_COMMENTS_URL } from '../../../../../common/constants';
|
||||
import { CommentType } from '../../../../../common/api';
|
||||
|
||||
describe('POST comment', () => {
|
||||
let routeHandler: RequestHandler<any, any, any>;
|
||||
|
@ -36,6 +37,7 @@ describe('POST comment', () => {
|
|||
},
|
||||
body: {
|
||||
comment: 'Wow, good luck catching that bad meanie!',
|
||||
type: CommentType.user,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -62,6 +64,7 @@ describe('POST comment', () => {
|
|||
},
|
||||
body: {
|
||||
comment: 'Wow, good luck catching that bad meanie!',
|
||||
type: CommentType.user,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -112,6 +115,7 @@ describe('POST comment', () => {
|
|||
},
|
||||
body: {
|
||||
comment: 'Wow, good luck catching that bad meanie!',
|
||||
type: CommentType.user,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -127,6 +131,7 @@ describe('POST comment', () => {
|
|||
expect(response.status).toEqual(200);
|
||||
expect(response.payload.comments[response.payload.comments.length - 1]).toEqual({
|
||||
comment: 'Wow, good luck catching that bad meanie!',
|
||||
type: CommentType.user,
|
||||
created_at: '2019-11-25T21:54:48.952Z',
|
||||
created_by: {
|
||||
email: null,
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
mockCaseComments,
|
||||
mockCaseNoConnectorId,
|
||||
} from './__fixtures__/mock_saved_objects';
|
||||
import { ConnectorTypes, ESCaseConnector } from '../../../common/api';
|
||||
import { ConnectorTypes, ESCaseConnector, CommentType } from '../../../common/api';
|
||||
|
||||
describe('Utils', () => {
|
||||
describe('transformNewCase', () => {
|
||||
|
@ -117,6 +117,7 @@ describe('Utils', () => {
|
|||
it('transforms correctly', () => {
|
||||
const comment = {
|
||||
comment: 'A comment',
|
||||
type: CommentType.user,
|
||||
createdDate: '2020-04-09T09:43:51.778Z',
|
||||
email: 'elastic@elastic.co',
|
||||
full_name: 'Elastic',
|
||||
|
@ -126,6 +127,7 @@ describe('Utils', () => {
|
|||
const res = transformNewComment(comment);
|
||||
expect(res).toEqual({
|
||||
comment: 'A comment',
|
||||
type: CommentType.user,
|
||||
created_at: '2020-04-09T09:43:51.778Z',
|
||||
created_by: { email: 'elastic@elastic.co', full_name: 'Elastic', username: 'elastic' },
|
||||
pushed_at: null,
|
||||
|
@ -138,6 +140,7 @@ describe('Utils', () => {
|
|||
it('transform correctly without optional fields', () => {
|
||||
const comment = {
|
||||
comment: 'A comment',
|
||||
type: CommentType.user,
|
||||
createdDate: '2020-04-09T09:43:51.778Z',
|
||||
};
|
||||
|
||||
|
@ -145,6 +148,7 @@ describe('Utils', () => {
|
|||
|
||||
expect(res).toEqual({
|
||||
comment: 'A comment',
|
||||
type: CommentType.user,
|
||||
created_at: '2020-04-09T09:43:51.778Z',
|
||||
created_by: { email: undefined, full_name: undefined, username: undefined },
|
||||
pushed_at: null,
|
||||
|
@ -157,6 +161,7 @@ describe('Utils', () => {
|
|||
it('transform correctly with optional fields as null', () => {
|
||||
const comment = {
|
||||
comment: 'A comment',
|
||||
type: CommentType.user,
|
||||
createdDate: '2020-04-09T09:43:51.778Z',
|
||||
email: null,
|
||||
full_name: null,
|
||||
|
@ -167,6 +172,7 @@ describe('Utils', () => {
|
|||
|
||||
expect(res).toEqual({
|
||||
comment: 'A comment',
|
||||
type: CommentType.user,
|
||||
created_at: '2020-04-09T09:43:51.778Z',
|
||||
created_by: { email: null, full_name: null, username: null },
|
||||
pushed_at: null,
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
CommentAttributes,
|
||||
ESCaseConnector,
|
||||
ESCaseAttributes,
|
||||
CommentRequest,
|
||||
} from '../../../common/api';
|
||||
import { transformESConnectorToCaseConnector } from './cases/helpers';
|
||||
|
||||
|
@ -55,15 +56,16 @@ export const transformNewCase = ({
|
|||
updated_by: null,
|
||||
});
|
||||
|
||||
interface NewCommentArgs {
|
||||
comment: string;
|
||||
interface NewCommentArgs extends CommentRequest {
|
||||
createdDate: string;
|
||||
email?: string | null;
|
||||
full_name?: string | null;
|
||||
username?: string | null;
|
||||
}
|
||||
|
||||
export const transformNewComment = ({
|
||||
comment,
|
||||
type,
|
||||
createdDate,
|
||||
email,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
|
@ -71,6 +73,7 @@ export const transformNewComment = ({
|
|||
username,
|
||||
}: NewCommentArgs): CommentAttributes => ({
|
||||
comment,
|
||||
type,
|
||||
created_at: createdDate,
|
||||
created_by: { email, full_name, username },
|
||||
pushed_at: null,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { SavedObjectsType } from 'src/core/server';
|
||||
import { commentsMigrations } from './migrations';
|
||||
|
||||
export const CASE_COMMENT_SAVED_OBJECT = 'cases-comments';
|
||||
|
||||
|
@ -17,6 +18,9 @@ export const caseCommentSavedObjectType: SavedObjectsType = {
|
|||
comment: {
|
||||
type: 'text',
|
||||
},
|
||||
type: {
|
||||
type: 'keyword',
|
||||
},
|
||||
created_at: {
|
||||
type: 'date',
|
||||
},
|
||||
|
@ -67,4 +71,5 @@ export const caseCommentSavedObjectType: SavedObjectsType = {
|
|||
},
|
||||
},
|
||||
},
|
||||
migrations: commentsMigrations,
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc } from '../../../../../src/core/server';
|
||||
import { ConnectorTypes } from '../../common/api/connectors';
|
||||
import { ConnectorTypes, CommentType } from '../../common/api';
|
||||
|
||||
interface UnsanitizedCase {
|
||||
connector_id: string;
|
||||
|
@ -126,3 +126,27 @@ export const userActionsMigrations = {
|
|||
};
|
||||
},
|
||||
};
|
||||
|
||||
interface UnsanitizedComment {
|
||||
comment: string;
|
||||
}
|
||||
|
||||
interface SanitizedComment {
|
||||
comment: string;
|
||||
type: CommentType;
|
||||
}
|
||||
|
||||
export const commentsMigrations = {
|
||||
'7.11.0': (
|
||||
doc: SavedObjectUnsanitizedDoc<UnsanitizedComment>
|
||||
): SavedObjectSanitizedDoc<SanitizedComment> => {
|
||||
return {
|
||||
...doc,
|
||||
attributes: {
|
||||
...doc.attributes,
|
||||
type: CommentType.user,
|
||||
},
|
||||
references: doc.references || [],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import { TestProviders } from '../../../common/mock';
|
|||
import { getFormMock } from '../__mock__/form';
|
||||
import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';
|
||||
|
||||
import { CommentRequest, CommentType } from '../../../../../case/common/api';
|
||||
import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline';
|
||||
import { usePostComment } from '../../containers/use_post_comment';
|
||||
import { useForm } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form';
|
||||
|
@ -66,8 +67,9 @@ const defaultPostCommment = {
|
|||
postComment,
|
||||
};
|
||||
|
||||
const sampleData = {
|
||||
const sampleData: CommentRequest = {
|
||||
comment: 'what a cool comment',
|
||||
type: CommentType.user,
|
||||
};
|
||||
|
||||
describe('AddComment ', () => {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { EuiButton, EuiLoadingSpinner } from '@elastic/eui';
|
|||
import React, { useCallback, forwardRef, useImperativeHandle } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { CommentRequest } from '../../../../../case/common/api';
|
||||
import { CommentRequest, CommentType } from '../../../../../case/common/api';
|
||||
import { usePostComment } from '../../containers/use_post_comment';
|
||||
import { Case } from '../../containers/types';
|
||||
import { MarkdownEditorForm } from '../../../common/components/markdown_editor/eui_form';
|
||||
|
@ -27,6 +27,7 @@ const MySpinner = styled(EuiLoadingSpinner)`
|
|||
|
||||
const initialCommentValue: CommentRequest = {
|
||||
comment: '',
|
||||
type: CommentType.user,
|
||||
};
|
||||
|
||||
export interface AddCommentRefObject {
|
||||
|
@ -81,7 +82,7 @@ export const AddComment = React.memo(
|
|||
if (onCommentSaving != null) {
|
||||
onCommentSaving();
|
||||
}
|
||||
postComment(data, onCommentPosted);
|
||||
postComment({ ...data, type: CommentType.user }, onCommentPosted);
|
||||
reset();
|
||||
}
|
||||
}, [onCommentPosted, onCommentSaving, postComment, reset, submit]);
|
||||
|
|
|
@ -82,11 +82,11 @@ export const getCasesColumns = (
|
|||
<>
|
||||
<EuiAvatar
|
||||
className="userAction__circle"
|
||||
name={createdBy.fullName ? createdBy.fullName : createdBy.username ?? ''}
|
||||
name={createdBy.fullName ? createdBy.fullName : createdBy.username ?? i18n.UNKNOWN}
|
||||
size="s"
|
||||
/>
|
||||
<Spacer data-test-subj="case-table-column-createdBy">
|
||||
{createdBy.fullName ? createdBy.fullName : createdBy.username ?? ''}
|
||||
{createdBy.fullName ? createdBy.fullName : createdBy.username ?? i18n.UNKNOWN}
|
||||
</Spacer>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -152,10 +152,6 @@ export const EMAIL_BODY = (caseUrl: string) =>
|
|||
defaultMessage: 'Case reference: {caseUrl}',
|
||||
});
|
||||
|
||||
export const UNKNOWN = i18n.translate('xpack.securitySolution.case.caseView.unknown', {
|
||||
defaultMessage: 'Unknown',
|
||||
});
|
||||
|
||||
export const CHANGED_CONNECTOR_FIELD = i18n.translate(
|
||||
'xpack.securitySolution.case.caseView.fieldChanged',
|
||||
{
|
||||
|
|
|
@ -131,8 +131,8 @@ export const getUpdateAction = ({
|
|||
}): EuiCommentProps => ({
|
||||
username: (
|
||||
<UserActionUsernameWithAvatar
|
||||
username={action.actionBy.username ?? ''}
|
||||
fullName={action.actionBy.fullName ?? action.actionBy.username ?? ''}
|
||||
username={action.actionBy.username}
|
||||
fullName={action.actionBy.fullName}
|
||||
/>
|
||||
),
|
||||
type: 'update',
|
||||
|
|
|
@ -217,8 +217,8 @@ export const UserActionTree = React.memo(
|
|||
() => ({
|
||||
username: (
|
||||
<UserActionUsername
|
||||
username={caseData.createdBy.username ?? i18n.UNKNOWN}
|
||||
fullName={caseData.createdBy.fullName ?? caseData.createdBy.username ?? ''}
|
||||
username={caseData.createdBy.username}
|
||||
fullName={caseData.createdBy.fullName}
|
||||
/>
|
||||
),
|
||||
event: i18n.ADDED_DESCRIPTION,
|
||||
|
@ -270,8 +270,8 @@ export const UserActionTree = React.memo(
|
|||
{
|
||||
username: (
|
||||
<UserActionUsername
|
||||
username={comment.createdBy.username ?? ''}
|
||||
fullName={comment.createdBy.fullName ?? comment.createdBy.username ?? ''}
|
||||
username={comment.createdBy.username}
|
||||
fullName={comment.createdBy.fullName}
|
||||
/>
|
||||
),
|
||||
'data-test-subj': `comment-create-action-${comment.id}`,
|
||||
|
@ -418,17 +418,11 @@ export const UserActionTree = React.memo(
|
|||
const bottomActions = [
|
||||
{
|
||||
username: (
|
||||
<UserActionUsername
|
||||
username={currentUser != null ? currentUser.username ?? '' : ''}
|
||||
fullName={currentUser != null ? currentUser.fullName ?? '' : ''}
|
||||
/>
|
||||
<UserActionUsername username={currentUser?.username} fullName={currentUser?.fullName} />
|
||||
),
|
||||
'data-test-subj': 'add-comment',
|
||||
timelineIcon: (
|
||||
<UserActionAvatar
|
||||
username={currentUser != null ? currentUser.username ?? '' : ''}
|
||||
fullName={currentUser != null ? currentUser.fullName ?? '' : ''}
|
||||
/>
|
||||
<UserActionAvatar username={currentUser?.username} fullName={currentUser?.fullName} />
|
||||
),
|
||||
className: 'isEdit',
|
||||
children: MarkdownNewComment,
|
||||
|
|
|
@ -22,26 +22,18 @@ describe('UserActionAvatar ', () => {
|
|||
|
||||
it('it renders', async () => {
|
||||
expect(wrapper.find(`[data-test-subj="user-action-avatar"]`).first().exists()).toBeTruthy();
|
||||
expect(
|
||||
wrapper.find(`[data-test-subj="user-action-avatar-loading-spinner"]`).first().exists()
|
||||
).toBeFalsy();
|
||||
expect(wrapper.find(`[data-test-subj="user-action-avatar"]`).first().text()).toBe('E');
|
||||
});
|
||||
|
||||
it('it shows the username if the fullName is undefined', async () => {
|
||||
wrapper = mount(<UserActionAvatar username={'elastic'} />);
|
||||
expect(wrapper.find(`[data-test-subj="user-action-avatar"]`).first().exists()).toBeTruthy();
|
||||
expect(
|
||||
wrapper.find(`[data-test-subj="user-action-avatar-loading-spinner"]`).first().exists()
|
||||
).toBeFalsy();
|
||||
expect(wrapper.find(`[data-test-subj="user-action-avatar"]`).first().text()).toBe('e');
|
||||
});
|
||||
|
||||
it('shows the loading spinner when the username AND the fullName are undefined', async () => {
|
||||
it('shows unknown when the username AND the fullName are undefined', async () => {
|
||||
wrapper = mount(<UserActionAvatar />);
|
||||
expect(wrapper.find(`[data-test-subj="user-action-avatar"]`).first().exists()).toBeFalsy();
|
||||
expect(
|
||||
wrapper.find(`[data-test-subj="user-action-avatar-loading-spinner"]`).first().exists()
|
||||
).toBeTruthy();
|
||||
expect(wrapper.find(`[data-test-subj="user-action-avatar"]`).first().exists()).toBeTruthy();
|
||||
expect(wrapper.find(`[data-test-subj="user-action-avatar"]`).first().text()).toBe('U');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { EuiAvatar, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { EuiAvatar } from '@elastic/eui';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface UserActionAvatarProps {
|
||||
username?: string | null;
|
||||
|
@ -13,17 +15,9 @@ interface UserActionAvatarProps {
|
|||
}
|
||||
|
||||
const UserActionAvatarComponent = ({ username, fullName }: UserActionAvatarProps) => {
|
||||
const avatarName = fullName && fullName.length > 0 ? fullName : username ?? null;
|
||||
const avatarName = fullName && fullName.length > 0 ? fullName : username ?? i18n.UNKNOWN;
|
||||
|
||||
return (
|
||||
<>
|
||||
{avatarName ? (
|
||||
<EuiAvatar name={avatarName} data-test-subj={`user-action-avatar`} />
|
||||
) : (
|
||||
<EuiLoadingSpinner data-test-subj={`user-action-avatar-loading-spinner`} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
return <EuiAvatar name={avatarName} data-test-subj={`user-action-avatar`} />;
|
||||
};
|
||||
|
||||
export const UserActionAvatar = memo(UserActionAvatarComponent);
|
||||
|
|
|
@ -8,19 +8,22 @@ import React, { memo } from 'react';
|
|||
import { EuiToolTip } from '@elastic/eui';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface UserActionUsernameProps {
|
||||
username: string;
|
||||
fullName?: string;
|
||||
username?: string | null;
|
||||
fullName?: string | null;
|
||||
}
|
||||
|
||||
const UserActionUsernameComponent = ({ username, fullName }: UserActionUsernameProps) => {
|
||||
const tooltipContent = (isEmpty(fullName) ? username : fullName) ?? i18n.UNKNOWN;
|
||||
return (
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={<p>{isEmpty(fullName) ? username : fullName}</p>}
|
||||
content={<p>{tooltipContent}</p>}
|
||||
data-test-subj="user-action-username-tooltip"
|
||||
>
|
||||
<strong>{username}</strong>
|
||||
<strong>{username ?? i18n.UNKNOWN.toLowerCase()}</strong>
|
||||
</EuiToolTip>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,10 +9,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiAvatar } from '@elastic/eui';
|
|||
import { isEmpty } from 'lodash/fp';
|
||||
|
||||
import { UserActionUsername } from './user_action_username';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface UserActionUsernameWithAvatarProps {
|
||||
username: string;
|
||||
fullName?: string;
|
||||
username?: string | null;
|
||||
fullName?: string | null;
|
||||
}
|
||||
|
||||
const UserActionUsernameWithAvatarComponent = ({
|
||||
|
@ -29,7 +30,7 @@ const UserActionUsernameWithAvatarComponent = ({
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiAvatar
|
||||
size="s"
|
||||
name={isEmpty(fullName) ? username : fullName ?? ''}
|
||||
name={(isEmpty(fullName) ? username : fullName) ?? i18n.UNKNOWN}
|
||||
data-test-subj="user-action-username-avatar"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -51,7 +51,7 @@ import {
|
|||
|
||||
import { DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases';
|
||||
import * as i18n from './translations';
|
||||
import { ConnectorTypes } from '../../../../case/common/api/connectors';
|
||||
import { ConnectorTypes, CommentType } from '../../../../case/common/api';
|
||||
|
||||
const abortCtrl = new AbortController();
|
||||
const mockKibanaServices = KibanaServices.get as jest.Mock;
|
||||
|
@ -404,6 +404,7 @@ describe('Case Configuration API', () => {
|
|||
});
|
||||
const data = {
|
||||
comment: 'comment',
|
||||
type: CommentType.user,
|
||||
};
|
||||
|
||||
test('check url, method, signal', async () => {
|
||||
|
|
|
@ -17,7 +17,8 @@ import {
|
|||
CaseUserActionsResponse,
|
||||
CasesResponse,
|
||||
CasesFindResponse,
|
||||
} from '../../../../case/common/api/cases';
|
||||
CommentType,
|
||||
} from '../../../../case/common/api';
|
||||
import { UseGetCasesState, DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases';
|
||||
import { ConnectorTypes } from '../../../../case/common/api/connectors';
|
||||
export { connectorsMock } from './configure/mock';
|
||||
|
@ -42,6 +43,7 @@ export const tags: string[] = ['coke', 'pepsi'];
|
|||
|
||||
export const basicComment: Comment = {
|
||||
comment: 'Solve this fast!',
|
||||
type: CommentType.user,
|
||||
id: basicCommentId,
|
||||
createdAt: basicCreatedAt,
|
||||
createdBy: elasticUser,
|
||||
|
|
|
@ -4,7 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { User, UserActionField, UserAction, CaseConnector } from '../../../../case/common/api';
|
||||
import {
|
||||
User,
|
||||
UserActionField,
|
||||
UserAction,
|
||||
CaseConnector,
|
||||
CommentType,
|
||||
} from '../../../../case/common/api';
|
||||
|
||||
export { CaseConnector, ActionConnector } from '../../../../case/common/api';
|
||||
|
||||
|
@ -13,6 +19,7 @@ export interface Comment {
|
|||
createdAt: string;
|
||||
createdBy: ElasticUser;
|
||||
comment: string;
|
||||
type: CommentType;
|
||||
pushedAt: string | null;
|
||||
pushedBy: string | null;
|
||||
updatedAt: string | null;
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
*/
|
||||
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
|
||||
import { CommentType } from '../../../../case/common/api';
|
||||
import { usePostComment, UsePostComment } from './use_post_comment';
|
||||
import { basicCaseId } from './mock';
|
||||
import * as api from './api';
|
||||
|
@ -15,6 +17,7 @@ describe('usePostComment', () => {
|
|||
const abortCtrl = new AbortController();
|
||||
const samplePost = {
|
||||
comment: 'a comment',
|
||||
type: CommentType.user,
|
||||
};
|
||||
const updateCaseCallback = jest.fn();
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -234,3 +234,7 @@ export const EDIT_CONNECTOR = i18n.translate('xpack.securitySolution.case.caseVi
|
|||
export const NO_CONNECTOR = i18n.translate('xpack.securitySolution.case.common.noConnector', {
|
||||
defaultMessage: 'No connector selected',
|
||||
});
|
||||
|
||||
export const UNKNOWN = i18n.translate('xpack.securitySolution.case.caseView.unknown', {
|
||||
defaultMessage: 'Unknown',
|
||||
});
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { ActionTypeModel } from '../../../../../../triggers_actions_ui/public/types';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export function getActionType(): ActionTypeModel {
|
||||
return {
|
||||
id: '.case',
|
||||
iconClass: 'securityAnalyticsApp',
|
||||
selectMessage: i18n.CASE_CONNECTOR_DESC,
|
||||
actionTypeTitle: i18n.CASE_CONNECTOR_TITLE,
|
||||
validateConnector: () => ({ errors: {} }),
|
||||
validateParams: () => ({ errors: {} }),
|
||||
actionConnectorFields: null,
|
||||
actionParamsFields: null,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const CASE_CONNECTOR_DESC = i18n.translate(
|
||||
'xpack.securitySolution.case.components.case.selectMessageText',
|
||||
{
|
||||
defaultMessage: 'Create or update a case.',
|
||||
}
|
||||
);
|
||||
|
||||
export const CASE_CONNECTOR_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.case.components.case.actionTypeTitle',
|
||||
{
|
||||
defaultMessage: 'Cases',
|
||||
}
|
||||
);
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { getActionType as getCaseConnectorUI } from './case';
|
|
@ -62,6 +62,7 @@ import {
|
|||
IndexFieldsStrategyResponse,
|
||||
} from '../common/search_strategy/index_fields';
|
||||
import { SecurityAppStore } from './common/store/store';
|
||||
import { getCaseConnectorUI } from './common/lib/connectors';
|
||||
|
||||
export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, StartPlugins> {
|
||||
private kibanaVersion: string;
|
||||
|
@ -312,6 +313,8 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
},
|
||||
});
|
||||
|
||||
plugins.triggersActionsUi.actionTypeRegistry.register(getCaseConnectorUI());
|
||||
|
||||
return {
|
||||
resolver: async () => {
|
||||
/**
|
||||
|
|
|
@ -45,7 +45,7 @@ import { SectionLoading } from '../../components/section_loading';
|
|||
import { ConnectorAddModal } from './connector_add_modal';
|
||||
import { actionTypeCompare } from '../../lib/action_type_compare';
|
||||
import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled';
|
||||
import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants';
|
||||
import { VIEW_LICENSE_OPTIONS_LINK, DEFAULT_HIDDEN_ACTION_TYPES } from '../../../common/constants';
|
||||
import { hasSaveActionsCapability } from '../../lib/capabilities';
|
||||
|
||||
interface ActionAccordionFormProps {
|
||||
|
@ -579,6 +579,11 @@ export const ActionForm = ({
|
|||
const preconfiguredConnectors = connectors.filter((connector) => connector.isPreconfigured);
|
||||
actionTypeNodes = actionTypeRegistry
|
||||
.list()
|
||||
/**
|
||||
* TODO: Remove when cases connector is available across Kibana. Issue: https://github.com/elastic/kibana/issues/82502.
|
||||
* If actionTypes are set, hidden connectors are filtered out. Otherwise, they are not.
|
||||
*/
|
||||
.filter(({ id }) => actionTypes ?? !DEFAULT_HIDDEN_ACTION_TYPES.includes(id))
|
||||
.filter((item) => actionTypesIndex[item.id])
|
||||
.filter((item) => !!item.actionParamsFields)
|
||||
.sort((a, b) =>
|
||||
|
|
|
@ -9,3 +9,5 @@ export { AGGREGATION_TYPES, builtInAggregationTypes } from './aggregation_types'
|
|||
export { builtInGroupByTypes } from './group_by_types';
|
||||
|
||||
export const VIEW_LICENSE_OPTIONS_LINK = 'https://www.elastic.co/subscriptions';
|
||||
// TODO: Remove when cases connector is available across Kibana. Issue: https://github.com/elastic/kibana/issues/82502.
|
||||
export const DEFAULT_HIDDEN_ACTION_TYPES = ['.case'];
|
||||
|
|
|
@ -33,11 +33,13 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: patchedCase } = await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
const { body: comment } = await supertest
|
||||
.delete(`${CASES_URL}/${postedCase.id}/comments/${patchedCase.comments[0].id}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(204)
|
||||
.send();
|
||||
|
||||
expect(comment).to.eql({});
|
||||
|
@ -53,13 +55,15 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: patchedCase } = await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.delete(`${CASES_URL}/fake-id/comments/${patchedCase.comments[0].id}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(404);
|
||||
|
||||
expect(body.message).to.eql(
|
||||
`This comment ${patchedCase.comments[0].id} does not exist in fake-id).`
|
||||
);
|
||||
|
|
|
@ -29,21 +29,25 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
// post 2 comments
|
||||
await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
const { body: patchedCase } = await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
const { body: caseComments } = await supertest
|
||||
.get(`${CASES_URL}/${postedCase.id}/comments/_find`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send();
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(caseComments.comments).to.eql(patchedCase.comments);
|
||||
});
|
||||
|
@ -54,21 +58,25 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
// post 2 comments
|
||||
await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
const { body: patchedCase } = await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ comment: 'unique' });
|
||||
.send({ comment: 'unique', type: 'user' })
|
||||
.expect(200);
|
||||
|
||||
const { body: caseComments } = await supertest
|
||||
.get(`${CASES_URL}/${postedCase.id}/comments/_find?search=unique`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send();
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(caseComments.comments).to.eql([patchedCase.comments[1]]);
|
||||
});
|
||||
|
@ -79,10 +87,13 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.get(`${CASES_URL}/${postedCase.id}/comments/_find?perPage=true`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
|
|
@ -27,12 +27,14 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
const { body: patchedCase } = await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
const { body: comment } = await supertest
|
||||
.get(`${CASES_URL}/${postedCase.id}/comments/${patchedCase.comments[0].id}`)
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
import { CASES_URL } from '../../../../../../plugins/case/common/constants';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function createGetTests({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const esArchiver = getService('esArchiver');
|
||||
|
||||
describe('migrations', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('cases');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('cases');
|
||||
});
|
||||
|
||||
it('7.11.0 migrates cases comments', async () => {
|
||||
const { body: comment } = await supertest
|
||||
.get(
|
||||
`${CASES_URL}/e1900ac0-017f-11eb-93f8-d161651bf509/comments/da677740-1ac7-11eb-b5a3-25ee88122510`
|
||||
)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send();
|
||||
|
||||
expect(comment.type).to.eql('user');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -33,7 +33,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: patchedCase } = await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
const newComment = 'Well I decided to update my comment. So what? Deal with it.';
|
||||
const { body } = await supertest
|
||||
.patch(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
|
@ -42,7 +44,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
id: patchedCase.comments[0].id,
|
||||
version: patchedCase.comments[0].version,
|
||||
comment: newComment,
|
||||
});
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(body.comments[0].comment).to.eql(newComment);
|
||||
expect(body.updated_by).to.eql(defaultUser);
|
||||
});
|
||||
|
@ -51,7 +55,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.patch(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
@ -85,7 +91,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: patchedCase } = await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.patch(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
@ -107,7 +115,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: patchedCase } = await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
const newComment = 'Well I decided to update my comment. So what? Deal with it.';
|
||||
await supertest
|
||||
.patch(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
|
|
|
@ -33,7 +33,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: patchedCase } = await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
expect(patchedCase.comments[0].comment).to.eql(postCommentReq.comment);
|
||||
expect(patchedCase.updated_by).to.eql(defaultUser);
|
||||
});
|
||||
|
|
|
@ -27,7 +27,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.delete(`${CASES_URL}?ids=["${postedCase.id}"]`)
|
||||
|
@ -42,29 +43,34 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
const { body: patchedCase } = await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.get(`${CASES_URL}/${postedCase.id}/comments/${patchedCase.comments[0].id}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.delete(`${CASES_URL}?ids=["${postedCase.id}"]`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(204);
|
||||
|
||||
await supertest
|
||||
.get(`${CASES_URL}/${postedCase.id}/comments/${patchedCase.comments[0].id}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('unhappy path - 404s when case is not there', async () => {
|
||||
await supertest
|
||||
.delete(`${CASES_URL}?ids=["fake-id"]`)
|
||||
|
|
|
@ -33,9 +33,24 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
|
||||
it('should return cases', async () => {
|
||||
const { body: a } = await supertest.post(CASES_URL).set('kbn-xsrf', 'true').send(postCaseReq);
|
||||
const { body: b } = await supertest.post(CASES_URL).set('kbn-xsrf', 'true').send(postCaseReq);
|
||||
const { body: c } = await supertest.post(CASES_URL).set('kbn-xsrf', 'true').send(postCaseReq);
|
||||
const { body: a } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
const { body: b } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
const { body: c } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(`${CASES_URL}/_find?sortOrder=asc`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
@ -55,7 +70,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ ...postCaseReq, tags: ['unique'] });
|
||||
.send({ ...postCaseReq, tags: ['unique'] })
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(`${CASES_URL}/_find?sortOrder=asc&tags=unique`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
@ -74,17 +91,22 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
// post 2 comments
|
||||
await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
const { body: patchedCase } = await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(`${CASES_URL}/_find?sortOrder=asc`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
@ -110,7 +132,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.patch(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
@ -124,6 +148,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(`${CASES_URL}/_find?sortOrder=asc`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
|
|
@ -118,6 +118,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.patch(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
@ -139,6 +140,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.patch(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
@ -160,6 +162,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.patch(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
@ -181,6 +184,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.patch(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
|
|
@ -130,7 +130,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/_push`)
|
||||
|
@ -143,6 +144,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
external_url: 'external_url',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(body.comments[0].pushed_by).to.eql(defaultUser);
|
||||
});
|
||||
|
||||
|
|
|
@ -26,7 +26,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.patch(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
|
|
@ -26,7 +26,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ ...postCaseReq, tags: ['unique'] });
|
||||
.send({ ...postCaseReq, tags: ['unique'] })
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(CASE_TAGS_URL)
|
||||
|
|
|
@ -39,13 +39,15 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(`${CASES_URL}/${postedCase.id}/user_actions`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(body.length).to.eql(1);
|
||||
|
||||
expect(body[0].action_field).to.eql(['description', 'status', 'tags', 'title', 'connector']);
|
||||
|
@ -58,7 +60,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.patch(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
@ -78,6 +82,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(body.length).to.eql(2);
|
||||
expect(body[1].action_field).to.eql(['status']);
|
||||
expect(body[1].action).to.eql('update');
|
||||
|
@ -89,7 +94,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
const newConnector = {
|
||||
id: '123',
|
||||
|
@ -117,6 +123,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(body.length).to.eql(2);
|
||||
expect(body[1].action_field).to.eql(['connector']);
|
||||
expect(body[1].action).to.eql('update');
|
||||
|
@ -130,7 +137,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.patch(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
@ -150,6 +159,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(body.length).to.eql(3);
|
||||
expect(body[1].action_field).to.eql(['tags']);
|
||||
expect(body[1].action).to.eql('add');
|
||||
|
@ -165,7 +175,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
const newTitle = 'Such a great title';
|
||||
await supertest
|
||||
.patch(CASES_URL)
|
||||
|
@ -186,6 +198,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(body.length).to.eql(2);
|
||||
expect(body[1].action_field).to.eql(['title']);
|
||||
expect(body[1].action).to.eql('update');
|
||||
|
@ -197,7 +210,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
const newDesc = 'Such a great description';
|
||||
await supertest
|
||||
.patch(CASES_URL)
|
||||
|
@ -218,6 +233,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(body.length).to.eql(2);
|
||||
expect(body[1].action_field).to.eql(['description']);
|
||||
expect(body[1].action).to.eql('update');
|
||||
|
@ -229,19 +245,22 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(`${CASES_URL}/${postedCase.id}/user_actions`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
expect(body.length).to.eql(2);
|
||||
|
||||
expect(body.length).to.eql(2);
|
||||
expect(body[1].action_field).to.eql(['comment']);
|
||||
expect(body[1].action).to.eql('create');
|
||||
expect(body[1].old_value).to.eql(null);
|
||||
|
@ -252,11 +271,15 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const { body: postedCase } = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq);
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
const { body: patchedCase } = await supertest
|
||||
.post(`${CASES_URL}/${postedCase.id}/comments`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCommentReq);
|
||||
.send(postCommentReq)
|
||||
.expect(200);
|
||||
|
||||
const newComment = 'Well I decided to update my comment. So what? Deal with it.';
|
||||
await supertest.patch(`${CASES_URL}/${postedCase.id}/comments`).set('kbn-xsrf', 'true').send({
|
||||
id: patchedCase.comments[0].id,
|
||||
|
@ -269,8 +292,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
expect(body.length).to.eql(3);
|
||||
|
||||
expect(body.length).to.eql(3);
|
||||
expect(body[2].action_field).to.eql(['comment']);
|
||||
expect(body[2].action).to.eql('update');
|
||||
expect(body[2].old_value).to.eql(postCommentReq.comment);
|
||||
|
@ -329,8 +352,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
expect(body.length).to.eql(2);
|
||||
|
||||
expect(body.length).to.eql(2);
|
||||
expect(body[1].action_field).to.eql(['pushed']);
|
||||
expect(body[1].action).to.eql('push-to-service');
|
||||
expect(body[1].old_value).to.eql(null);
|
||||
|
|
763
x-pack/test/case_api_integration/basic/tests/connectors/case.ts
Normal file
763
x-pack/test/case_api_integration/basic/tests/connectors/case.ts
Normal file
|
@ -0,0 +1,763 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
|
||||
import { CASES_URL } from '../../../../../plugins/case/common/constants';
|
||||
import {
|
||||
postCaseReq,
|
||||
postCaseResp,
|
||||
removeServerGeneratedPropertiesFromCase,
|
||||
removeServerGeneratedPropertiesFromComments,
|
||||
} from '../../../common/lib/mock';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getService }: FtrProviderContext): void => {
|
||||
const supertest = getService('supertest');
|
||||
|
||||
describe('case_connector', () => {
|
||||
let createdActionId = '';
|
||||
|
||||
it('should return 200 when creating a case action successfully', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
|
||||
expect(createdAction).to.eql({
|
||||
id: createdActionId,
|
||||
isPreconfigured: false,
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
});
|
||||
|
||||
const { body: fetchedAction } = await supertest
|
||||
.get(`/api/actions/action/${createdActionId}`)
|
||||
.expect(200);
|
||||
|
||||
expect(fetchedAction).to.eql({
|
||||
id: fetchedAction.id,
|
||||
isPreconfigured: false,
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should respond with a 400 Bad Request when creating a case without title', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
const params = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
tags: ['case', 'connector'],
|
||||
description: 'case description',
|
||||
connector: {
|
||||
id: 'jira',
|
||||
name: 'Jira',
|
||||
type: '.jira',
|
||||
fields: {
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const caseConnector = await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
expect(caseConnector.body).to.eql({
|
||||
status: 'error',
|
||||
actionId: createdActionId,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subActionParams.title]: expected value of type [string] but got [undefined]\n- [1.subAction]: expected value to equal [update]\n- [2.subAction]: expected value to equal [addComment]',
|
||||
retry: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should respond with a 400 Bad Request when creating a case without description', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
const params = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
connector: {
|
||||
id: 'jira',
|
||||
name: 'Jira',
|
||||
type: '.jira',
|
||||
fields: {
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const caseConnector = await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
expect(caseConnector.body).to.eql({
|
||||
status: 'error',
|
||||
actionId: createdActionId,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subActionParams.description]: expected value of type [string] but got [undefined]\n- [1.subAction]: expected value to equal [update]\n- [2.subAction]: expected value to equal [addComment]',
|
||||
retry: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should respond with a 400 Bad Request when creating a case without tags', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
const params = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
description: 'case description',
|
||||
connector: {
|
||||
id: 'jira',
|
||||
name: 'Jira',
|
||||
type: '.jira',
|
||||
fields: {
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const caseConnector = await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
expect(caseConnector.body).to.eql({
|
||||
status: 'error',
|
||||
actionId: createdActionId,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subActionParams.tags]: expected value of type [array] but got [undefined]\n- [1.subAction]: expected value to equal [update]\n- [2.subAction]: expected value to equal [addComment]',
|
||||
retry: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should respond with a 400 Bad Request when creating a case without connector', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
const params = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
description: 'case description',
|
||||
tags: ['case', 'connector'],
|
||||
},
|
||||
};
|
||||
|
||||
const caseConnector = await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
expect(caseConnector.body).to.eql({
|
||||
status: 'error',
|
||||
actionId: createdActionId,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subActionParams.connector.id]: expected value of type [string] but got [undefined]\n- [1.subAction]: expected value to equal [update]\n- [2.subAction]: expected value to equal [addComment]',
|
||||
retry: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should respond with a 400 Bad Request when creating jira without issueType', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
const params = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
description: 'case description',
|
||||
tags: ['case', 'connector'],
|
||||
connector: {
|
||||
id: 'jira',
|
||||
name: 'Jira',
|
||||
type: '.jira',
|
||||
fields: {
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const caseConnector = await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
expect(caseConnector.body).to.eql({
|
||||
status: 'error',
|
||||
actionId: createdActionId,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subActionParams.connector.fields.issueType]: expected value of type [string] but got [undefined]\n- [1.subAction]: expected value to equal [update]\n- [2.subAction]: expected value to equal [addComment]',
|
||||
retry: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should respond with a 400 Bad Request when creating a connector with wrong fields', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
const params = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
description: 'case description',
|
||||
tags: ['case', 'connector'],
|
||||
connector: {
|
||||
id: 'servicenow',
|
||||
name: 'Servicenow',
|
||||
type: '.servicenow',
|
||||
fields: {
|
||||
impact: 'Medium',
|
||||
severity: 'Medium',
|
||||
notExists: 'not-exists',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const caseConnector = await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
expect(caseConnector.body).to.eql({
|
||||
status: 'error',
|
||||
actionId: createdActionId,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subActionParams.connector.fields.notExists]: definition for this key is missing\n- [1.subAction]: expected value to equal [update]\n- [2.subAction]: expected value to equal [addComment]',
|
||||
retry: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should respond with a 400 Bad Request when creating a none without fields as null', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
const params = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
description: 'case description',
|
||||
tags: ['case', 'connector'],
|
||||
connector: {
|
||||
id: 'none',
|
||||
name: 'None',
|
||||
type: '.none',
|
||||
fields: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const caseConnector = await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
expect(caseConnector.body).to.eql({
|
||||
status: 'error',
|
||||
actionId: createdActionId,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subActionParams.connector]: Fields must be set to null for connectors of type .none\n- [1.subAction]: expected value to equal [update]\n- [2.subAction]: expected value to equal [addComment]',
|
||||
retry: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a case', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
const params = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'case description',
|
||||
connector: {
|
||||
id: 'jira',
|
||||
name: 'Jira',
|
||||
type: '.jira',
|
||||
fields: {
|
||||
issueType: '10006',
|
||||
priority: 'High',
|
||||
parent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const caseConnector = await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(`${CASES_URL}/${caseConnector.body.data.id}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
const data = removeServerGeneratedPropertiesFromCase(body);
|
||||
expect(data).to.eql({
|
||||
...postCaseResp(caseConnector.body.data.id),
|
||||
...params.subActionParams,
|
||||
created_by: {
|
||||
email: null,
|
||||
full_name: null,
|
||||
username: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a case with connector with field as null if not provided', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
const params = {
|
||||
subAction: 'create',
|
||||
subActionParams: {
|
||||
title: 'Case from case connector!!',
|
||||
tags: ['case', 'connector'],
|
||||
description: 'case description',
|
||||
connector: {
|
||||
id: 'servicenow',
|
||||
name: 'Servicenow',
|
||||
type: '.servicenow',
|
||||
fields: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const caseConnector = await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(`${CASES_URL}/${caseConnector.body.data.id}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
const data = removeServerGeneratedPropertiesFromCase(body);
|
||||
expect(data).to.eql({
|
||||
...postCaseResp(caseConnector.body.data.id),
|
||||
...params.subActionParams,
|
||||
connector: {
|
||||
id: 'servicenow',
|
||||
name: 'Servicenow',
|
||||
type: '.servicenow',
|
||||
fields: {
|
||||
impact: null,
|
||||
severity: null,
|
||||
urgency: null,
|
||||
},
|
||||
},
|
||||
created_by: {
|
||||
email: null,
|
||||
full_name: null,
|
||||
username: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should respond with a 400 Bad Request when updating a case without id', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
const params = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
version: '123',
|
||||
title: 'Case from case connector!!',
|
||||
},
|
||||
};
|
||||
|
||||
const caseConnector = await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
expect(caseConnector.body).to.eql({
|
||||
status: 'error',
|
||||
actionId: createdActionId,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [create]\n- [1.subActionParams.id]: expected value of type [string] but got [undefined]\n- [2.subAction]: expected value to equal [addComment]',
|
||||
retry: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should respond with a 400 Bad Request when updating a case without version', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
const params = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
id: '123',
|
||||
title: 'Case from case connector!!',
|
||||
},
|
||||
};
|
||||
|
||||
const caseConnector = await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
expect(caseConnector.body).to.eql({
|
||||
status: 'error',
|
||||
actionId: createdActionId,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [create]\n- [1.subActionParams.version]: expected value of type [string] but got [undefined]\n- [2.subAction]: expected value to equal [addComment]',
|
||||
retry: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should update a case', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
|
||||
const caseRes = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
const params = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
id: caseRes.body.id,
|
||||
version: caseRes.body.version,
|
||||
title: 'Case from case connector!!',
|
||||
},
|
||||
};
|
||||
|
||||
await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(`${CASES_URL}/${caseRes.body.id}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
const data = removeServerGeneratedPropertiesFromCase(body);
|
||||
expect(data).to.eql({
|
||||
...postCaseResp(caseRes.body.id),
|
||||
title: 'Case from case connector!!',
|
||||
updated_by: {
|
||||
email: null,
|
||||
full_name: null,
|
||||
username: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addComment', () => {
|
||||
it('should respond with a 400 Bad Request when adding a comment to a case without caseId', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
const params = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
comment: { comment: 'a comment', type: 'user' },
|
||||
},
|
||||
};
|
||||
|
||||
const caseConnector = await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
expect(caseConnector.body).to.eql({
|
||||
status: 'error',
|
||||
actionId: createdActionId,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [create]\n- [1.subActionParams.id]: expected value of type [string] but got [undefined]\n- [2.subAction]: expected value to equal [addComment]',
|
||||
retry: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should respond with a 400 Bad Request when adding a comment to a case without comment', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
const params = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
caseId: '123',
|
||||
},
|
||||
};
|
||||
|
||||
const caseConnector = await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
expect(caseConnector.body).to.eql({
|
||||
status: 'error',
|
||||
actionId: createdActionId,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [create]\n- [1.subActionParams.id]: expected value of type [string] but got [undefined]\n- [2.subAction]: expected value to equal [addComment]',
|
||||
retry: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should respond with a 400 Bad Request when adding a comment to a case without comment type', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
const params = {
|
||||
subAction: 'update',
|
||||
subActionParams: {
|
||||
caseId: '123',
|
||||
comment: { comment: 'a comment' },
|
||||
},
|
||||
};
|
||||
|
||||
const caseConnector = await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
expect(caseConnector.body).to.eql({
|
||||
status: 'error',
|
||||
actionId: createdActionId,
|
||||
message:
|
||||
'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [create]\n- [1.subActionParams.id]: expected value of type [string] but got [undefined]\n- [2.subAction]: expected value to equal [addComment]',
|
||||
retry: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a comment', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'A case connector',
|
||||
actionTypeId: '.case',
|
||||
config: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
createdActionId = createdAction.id;
|
||||
|
||||
const caseRes = await supertest
|
||||
.post(CASES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(postCaseReq)
|
||||
.expect(200);
|
||||
|
||||
const params = {
|
||||
subAction: 'addComment',
|
||||
subActionParams: {
|
||||
caseId: caseRes.body.id,
|
||||
comment: { comment: 'a comment', type: 'user' },
|
||||
},
|
||||
};
|
||||
|
||||
await supertest
|
||||
.post(`/api/actions/action/${createdActionId}/_execute`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({ params })
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(`${CASES_URL}/${caseRes.body.id}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
const data = removeServerGeneratedPropertiesFromCase(body);
|
||||
const comments = removeServerGeneratedPropertiesFromComments(data.comments ?? []);
|
||||
expect({ ...data, comments }).to.eql({
|
||||
...postCaseResp(caseRes.body.id),
|
||||
comments,
|
||||
totalComment: 1,
|
||||
updated_by: {
|
||||
email: null,
|
||||
full_name: null,
|
||||
username: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
|
@ -31,6 +31,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => {
|
|||
loadTestFile(require.resolve('./configure/get_connectors'));
|
||||
loadTestFile(require.resolve('./configure/patch_configure'));
|
||||
loadTestFile(require.resolve('./configure/post_configure'));
|
||||
loadTestFile(require.resolve('./connectors/case'));
|
||||
|
||||
// Migrations
|
||||
loadTestFile(require.resolve('./cases/migrations'));
|
||||
|
|
|
@ -26,6 +26,7 @@ const enabledActionTypes = [
|
|||
'.servicenow',
|
||||
'.slack',
|
||||
'.webhook',
|
||||
'.case',
|
||||
'test.authorization',
|
||||
'test.failing',
|
||||
'test.index-record',
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
CasePostRequest,
|
||||
CaseResponse,
|
||||
CasesFindResponse,
|
||||
CommentResponse,
|
||||
ConnectorTypes,
|
||||
} from '../../../../plugins/case/common/api';
|
||||
export const defaultUser = { email: null, full_name: null, username: 'elastic' };
|
||||
|
@ -23,12 +24,16 @@ export const postCaseReq: CasePostRequest = {
|
|||
},
|
||||
};
|
||||
|
||||
export const postCommentReq: { comment: string } = {
|
||||
export const postCommentReq: { comment: string; type: string } = {
|
||||
comment: 'This is a cool comment',
|
||||
type: 'user',
|
||||
};
|
||||
|
||||
export const postCaseResp = (id: string): Partial<CaseResponse> => ({
|
||||
...postCaseReq,
|
||||
export const postCaseResp = (
|
||||
id: string,
|
||||
req: CasePostRequest = postCaseReq
|
||||
): Partial<CaseResponse> => ({
|
||||
...req,
|
||||
id,
|
||||
comments: [],
|
||||
totalComment: 0,
|
||||
|
@ -47,6 +52,16 @@ export const removeServerGeneratedPropertiesFromCase = (
|
|||
return rest;
|
||||
};
|
||||
|
||||
export const removeServerGeneratedPropertiesFromComments = (
|
||||
comments: CommentResponse[]
|
||||
): Array<Partial<CommentResponse>> => {
|
||||
return comments.map((comment) => {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { created_at, updated_at, version, ...rest } = comment;
|
||||
return rest;
|
||||
});
|
||||
};
|
||||
|
||||
export const findCasesResp: CasesFindResponse = {
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
|
|
|
@ -137,3 +137,75 @@
|
|||
"type": "_doc"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "cases-comments:da677740-1ac7-11eb-b5a3-25ee88122510",
|
||||
"index": ".kibana_1",
|
||||
"source": {
|
||||
"cases-comments": {
|
||||
"comment": "This is a cool comment",
|
||||
"created_at": "2020-10-30T15:52:02.984Z",
|
||||
"created_by": {
|
||||
"email": null,
|
||||
"full_name": null,
|
||||
"username": "elastic"
|
||||
},
|
||||
"pushed_at": null,
|
||||
"pushed_by": null,
|
||||
"updated_at": null,
|
||||
"updated_by": null
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"id": "e1900ac0-017f-11eb-93f8-d161651bf509",
|
||||
"name": "associated-cases",
|
||||
"type": "cases"
|
||||
}
|
||||
],
|
||||
"type": "cases-comments",
|
||||
"updated_at": "2020-10-30T15:52:02.996Z"
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "cases-user-actions:db027ec0-1ac7-11eb-b5a3-25ee88122510",
|
||||
"index": ".kibana_1",
|
||||
"source": {
|
||||
"cases-user-actions": {
|
||||
"action": "create",
|
||||
"action_at": "2020-10-30T15:52:02.984Z",
|
||||
"action_by": {
|
||||
"email": null,
|
||||
"full_name": null,
|
||||
"username": "elastic"
|
||||
},
|
||||
"action_field": [
|
||||
"comment"
|
||||
],
|
||||
"new_value": "This is a cool comment",
|
||||
"old_value": null
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"id": "e1900ac0-017f-11eb-93f8-d161651bf509",
|
||||
"name": "associated-cases",
|
||||
"type": "cases"
|
||||
},
|
||||
{
|
||||
"id": "da677740-1ac7-11eb-b5a3-25ee88122510",
|
||||
"name": "associated-cases-comments",
|
||||
"type": "cases-comments"
|
||||
}
|
||||
],
|
||||
"type": "cases-user-actions",
|
||||
"updated_at": "2020-10-30T15:52:04.012Z"
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue